diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Http.php b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Http.php index 788932ae0..72009a623 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Http.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Http.php @@ -115,11 +115,10 @@ class Http if (!\strlen($sHost)) { $sName = static::GetServer('SERVER_NAME', ''); $iPort = (int) static::GetServer('SERVER_PORT', 80); - $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); } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php index 133622dd7..71fe68ed2 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php @@ -685,19 +685,6 @@ abstract class Utils 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 { return !empty($sIp) && $sIp === \filter_var($sIp, FILTER_VALIDATE_IP); @@ -726,9 +713,6 @@ abstract class Utils { $aParts = \explode('@', $sStr); $sDomain = \array_pop($aParts); - if ($bLowerCase) { - $sDomain = \mb_strtolower($sDomain); - } if (\strlen($sDomain) && \preg_match('/[^\x20-\x7E]/', $sDomain)) { try { @@ -736,6 +720,9 @@ abstract class Utils } catch (\Throwable $oException) {} } + if ($bLowerCase) { + $sDomain = \strtolower($sDomain); + } $aParts[] = $sDomain; return \implode('@', $aParts); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php index ba3f841d5..8850a52d0 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php @@ -33,55 +33,44 @@ trait UserAuth $this->Plugins()->RunHook('login.credentials.step-1', array(&$sEmail)); if (!\str_contains($sEmail, '@')) { - $this->logWrite('The email address "' . $sEmail . '" is not complete', \LOG_INFO, 'LOGIN'); - - $bAdded = false; - + $this->logWrite("The email address '{$sEmail}' is incomplete", \LOG_INFO, 'LOGIN'); + $oDomain = null; if ($this->Config()->Get('login', 'determine_user_domain', false)) { - $sUserHost = \trim($this->Http()->GetHost(true, true)); - $this->logWrite('Determined user domain: ' . $sUserHost, \LOG_INFO, 'LOGIN'); + $sUserHost = \strtolower(\idn_to_ascii($this->Http()->GetHost(false, true))); + $this->logWrite("Determined user domain: {$sUserHost}", \LOG_INFO, 'LOGIN'); $aDomainParts = \explode('.', $sUserHost); $iLimit = \min(\count($aDomainParts), 14); - $oDomainProvider = $this->DomainProvider(); while (0 < $iLimit--) { $sLine = \implode('.', $aDomainParts); - $oDomain = $oDomainProvider->Load($sLine, false); if ($oDomain) { - $bAdded = true; - $this->logWrite('Check "' . $sLine . '": OK (' . $sEmail . ' > ' . $sEmail . '@' . $sLine . ')', - \LOG_INFO, 'LOGIN'); - $sEmail .= '@' . $sLine; + $this->logWrite("Check '{$sLine}': OK", \LOG_INFO, 'LOGIN'); break; } else { - $this->logWrite('Check "' . $sLine . '": NO', \LOG_INFO, 'LOGIN'); + $this->logWrite("Check '{$sLine}': NO", \LOG_INFO, 'LOGIN'); } - \array_shift($aDomainParts); } - if (!$bAdded) { + if (!$oDomain) { $oDomain = $oDomainProvider->Load($sUserHost, true); if ($oDomain) { - $bAdded = true; - $this->logWrite('Check "' . $sUserHost . '" with wildcard: OK (' . $sEmail . ' > ' . $sEmail . '@' . $sUserHost . ')', - \LOG_INFO, 'LOGIN'); - $sEmail .= '@' . $sUserHost; + $this->logWrite("Check '{$sUserHost}' with wildcard: OK", \LOG_INFO, 'LOGIN'); } else { - $this->logWrite('Check "' . $sUserHost . '" with wildcard: NO', \LOG_INFO, 'LOGIN'); + $this->logWrite("Check '{$sUserHost}' with wildcard: NO", \LOG_INFO, 'LOGIN'); } } - if (!$bAdded) { - $this->logWrite('Domain was not found!', \LOG_INFO, 'LOGIN'); + if (!$oDomain) { + $this->logWrite("Domain '{$sUserHost}' was not determined!", \LOG_INFO, 'LOGIN'); } } - if (!$bAdded) { + if (!$oDomain) { $sDefDomain = \trim($this->Config()->Get('login', 'default_domain', '')); if (\strlen($sDefDomain)) { if ('HTTP_HOST' === $sDefDomain || 'SERVER_NAME' === $sDefDomain) { @@ -89,10 +78,8 @@ trait UserAuth } else if ('gethostname' === $sDefDomain) { $sDefDomain = \gethostname(); } - $this->logWrite('Default domain "' . $sDefDomain . '" was used. (' . $sEmail . ' > ' . $sEmail . '@' . $sDefDomain . ')', - \LOG_INFO, 'LOGIN'); - $sEmail .= '@' . $sDefDomain; + $this->logWrite("Default domain '{$sDefDomain}' will be used.", \LOG_INFO, 'LOGIN'); } else { $this->logWrite('Default domain not configured.', \LOG_INFO, 'LOGIN'); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Domain.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Domain.php index 21c1107ac..d40b7ca7d 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Domain.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Domain.php @@ -1,4 +1,8 @@ Name = $sName; - + $this->Name = \strtolower(\idn_to_ascii($sName)); $this->IMAP = new \MailSo\Imap\Settings; $this->SMTP = new \MailSo\Smtp\Settings; $this->Sieve = new \MailSo\Sieve\Settings; @@ -96,7 +99,7 @@ class Domain implements \JsonSerializable public function SetAliasName(string $sAliasName) : void { - $this->aliasName = $sAliasName; + $this->aliasName = \strtolower(\idn_to_ascii($sAliasName)); } public function ValidateWhiteList(string $sEmail, string $sLogin = '') : bool @@ -232,14 +235,14 @@ class Domain implements \JsonSerializable { $aResult = array( // '@Object' => 'Object/Domain', - 'name' => $this->Name, + 'name' => \idn_to_utf8($this->Name), 'IMAP' => $this->IMAP, 'SMTP' => $this->SMTP, 'Sieve' => $this->Sieve, 'whiteList' => $this->whiteList ); if ($this->aliasName) { - $aResult['aliasName'] = $this->aliasName; + $aResult['aliasName'] = \idn_to_utf8($this->aliasName); } return $aResult; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php index 9c101fe50..eb0fb9124 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php @@ -10,6 +10,7 @@ abstract class Autoconfig public static function discover(string $emailaddress) : ?array { $domain = \MailSo\Base\Utils::GetDomainFromEmail($emailaddress); +// $domain = \strtolower(\idn_to_ascii($domain)); // First try $autoconfig = static::resolve($domain, $emailaddress); if ($autoconfig) { @@ -89,7 +90,7 @@ abstract class Autoconfig private static function publicsuffixes() : array { $oCache = \RainLoop\Api::Actions()->Cacher(); - $list = $oCache->Get('public_suffix_list') ?: null; + $list = $oCache->Get('public_suffix_list'); if ($list) { $list = \json_decode($list, true); if ($list[1] < \time()) { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php index 3b812e7f3..e34481554 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php @@ -1,4 +1,8 @@ oCacher) { $sResult = $this->oCacher->Get(static::CACHE_KEY); - if (\strlen($sResult)) { + if (\is_string($sResult)) { return $sResult; } } $aNames = array(); -// $aList = \glob($this->sDomainPath.'/*.{ini,json}', GLOB_BRACE); +// $aList = \glob($this->sDomainPath.'/*.{ini,json,alias}', GLOB_BRACE); $aList = \glob($this->sDomainPath.'/*.json'); foreach ($aList as $sFile) { $sName = \substr(\basename($sFile), 0, -5); @@ -72,16 +72,14 @@ class DefaultDomain implements DomainInterface $sResult = \implode(' ', \array_unique($aNames)); } - if ($this->oCacher) { - $this->oCacher->Set(static::CACHE_KEY, $sResult); - } + $this->oCacher && $this->oCacher->Set(static::CACHE_KEY, $sResult); return $sResult; } 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())) { return null; } @@ -130,7 +128,7 @@ class DefaultDomain implements DomainInterface 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); \RainLoop\Utils::saveFile($this->sDomainPath.'/'.$sRealFileName.'.alias', $sAlias); $this->oCacher && $this->oCacher->Delete(static::CACHE_KEY); @@ -147,7 +145,7 @@ class DefaultDomain implements DomainInterface // RainLoop use comma, we use newline $sItem = \strtok($sFile, ",\n"); while (false !== $sItem) { - $aDisabled[] = \MailSo\Base\Utils::IdnToUtf8($sItem, true); + $aDisabled[] = \strtolower(\idn_to_ascii($sItem)); $sItem = \strtok(",\n"); } return $aDisabled; @@ -156,7 +154,7 @@ class DefaultDomain implements DomainInterface public function Disable(string $sName, bool $bDisable) : bool { - $sName = \MailSo\Base\Utils::IdnToUtf8($sName, true); + $sName = \strtolower(\idn_to_ascii($sName)); if ($sName) { $aResult = $this->getDisabled(); if ($bDisable) { @@ -209,20 +207,23 @@ class DefaultDomain implements DomainInterface if ($bAlias) { if ($bIncludeAliases) { $aAliases[$sName] = array( - 'name' => $sName, + 'name' => \idn_to_utf8($sName), + 'punycode' => $sName, 'disabled' => \in_array($sName, $aDisabledNames), 'alias' => true ); } } else if (false !== \strpos($sName, '*')) { $aWildCards[$sName] = array( - 'name' => $sName, + 'name' => \idn_to_utf8($sName), + 'punycode' => $sName, 'disabled' => \in_array($sName, $aDisabledNames), 'alias' => false ); } else { $aResult[$sName] = array( - 'name' => $sName, + 'name' => \idn_to_utf8($sName), + 'punycode' => $sName, 'disabled' => \in_array($sName, $aDisabledNames), 'alias' => false ); diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/idn.php b/snappymail/v/0.0.0/app/libraries/snappymail/idn.php index 3a7762e31..25f6c029a 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/idn.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/idn.php @@ -9,10 +9,10 @@ abstract class IDN { public static function anyToAscii(string $string) : string { - if (\preg_match('#[:/]#', $string)) { + if (\strspn($string, ':/')) { return static::uri($string, true); } - if (\strpos($string, '@')) { + if (\str_contains($string, '@')) { return static::emailAddress($string, true); } return \idn_to_ascii($string); @@ -20,91 +20,73 @@ abstract class IDN public static function anyToUtf8(string $string) : string { - if (\preg_match('#[:/]#', $string)) { + if (\strspn($string, ':/')) { return static::uri($string, false); } - if (\strpos($string, '@')) { + if (\str_contains($string, '@')) { return static::emailAddress($string, false); } return \idn_to_utf8($string); } + /** + * Converts IDN domain part to lowercased punycode + */ public static function emailToAscii(string $address) : string { 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); - } - - public static function uriToAscii(string $address) : string - { - return static::uri($address, true); - } - - public static function uriToUtf8(string $address) : string - { - return static::uri($address, false); + return $toAscii ? \strtolower(\idn_to_ascii($domain)) : \idn_to_utf8($domain); +/* + $domain = \explode('.', $domain); + foreach ($domain as $k => $v) { + $conv = $toAscii ? \idn_to_ascii($v) : \idn_to_utf8($v); + if ($conv) $domain[$k] = $conv; + } + return \implode('.', $domain); +*/ } private static function emailAddress(string $address, bool $toAscii) : string { - if (\str_contains($address, '@')) { - $local = \explode('@', $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); + if (!\str_contains($address, '@')) { + throw new \RuntimeException("Invalid email address: {$address}"); } - 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 { - if (\preg_match('#[:\./]#', $address)) { - $parsed = \parse_url($address); - if (isset($parsed['host'])) { - $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); + $parsed = \parse_url($address); + if (isset($parsed['host'])) { + $parsed['host'] = static::domain($parsed['host'], $toAscii); - $url = '//'; - if (!empty($parsed['scheme'])) { $url = $parsed['scheme'] . (\strtolower($parsed['scheme']) === 'mailto' ? ':' : '://'); } + $url = empty($parsed['scheme']) ? '//' + : $parsed['scheme'] . (\strtolower($parsed['scheme']) === 'mailto' ? ':' : '://'); /* - if (!empty($parsed['user']) || !empty($parsed['pass'])) { - $ret_url .= $parts_arr['user']; - if (!empty($parts_arr['pass'])) { - $ret_url .= ':' . $parts_arr['pass']; - } - $ret_url .= '@'; + if (!empty($parsed['user']) || !empty($parsed['pass'])) { + $ret_url .= $parts_arr['user']; + if (!empty($parts_arr['pass'])) { + $ret_url .= ':' . $parts_arr['pass']; } + $ret_url .= '@'; + } */ - if (!empty($parsed['host'])) { $url .= $parsed['host']; } - if (!empty($parsed['port'])) { $url .= ':'.$parsed['port']; } - if (!empty($parsed['path'])) { $url .= $parsed['path']; } - if (!empty($parsed['query'])) { $url .= '?'.$parsed['query']; } - if (!empty($parsed['fragment'])) { $url .= '#'.$parsed['fragment']; } - 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); + if (!empty($parsed['host'])) { $url .= $parsed['host']; } + if (!empty($parsed['port'])) { $url .= ':'.$parsed['port']; } + if (!empty($parsed['path'])) { $url .= $parsed['path']; } + if (!empty($parsed['query'])) { $url .= '?'.$parsed['query']; } + if (!empty($parsed['fragment'])) { $url .= '#'.$parsed['fragment']; } + return $url; } - return $toAscii ? \idn_to_ascii($address) : \idn_to_utf8($address); + + // parse_url seems to have failed, try without it + return static::domain($address, $toAscii); } }