mirror of
https://github.com/the-djmaze/snappymail.git
synced 2024-09-20 07:35:55 +08:00
Improved handling of Internationalized Domain Names in punycode
This commit is contained in:
parent
fd54796710
commit
696a2bbd3c
|
@ -35,7 +35,9 @@ module.exports = {
|
|||
// vendors/bootstrap/bootstrap.native.js
|
||||
'BSN': "readonly",
|
||||
// Mailvelope
|
||||
'mailvelope': "readonly"
|
||||
'mailvelope': "readonly",
|
||||
// Punycode
|
||||
'IDN': "readonly"
|
||||
},
|
||||
// http://eslint.org/docs/rules/
|
||||
rules: {
|
||||
|
|
|
@ -28,6 +28,10 @@ export class AccountModel extends AbstractModel {
|
|||
&& setTimeout(()=>this.fetchUnread(), (Math.ceil(Math.random() * 10)) * 3000);
|
||||
}
|
||||
|
||||
label() {
|
||||
return this.name || IDN.toUnicode(this.email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get INBOX unread messages
|
||||
*/
|
||||
|
|
|
@ -13,6 +13,7 @@ DomainAdminStore.fetch = () => {
|
|||
if (!iError) {
|
||||
DomainAdminStore(
|
||||
data.Result.map(item => {
|
||||
item.name = IDN.toUnicode(item.name);
|
||||
item.disabled = ko.observable(item.disabled);
|
||||
item.askDelete = ko.observable(false);
|
||||
return item;
|
||||
|
|
|
@ -87,9 +87,8 @@ export class SystemDropDownUserView extends AbstractViewRight {
|
|||
}
|
||||
|
||||
accountName() {
|
||||
let email = AccountUserStore.email(),
|
||||
account = AccountUserStore.find(account => account.email == email);
|
||||
return account?.name || email;
|
||||
const email = AccountUserStore.email();
|
||||
return AccountUserStore.find(account => account.email == email)?.label() || IDN.toUnicode(email);
|
||||
}
|
||||
|
||||
settingsClick() {
|
||||
|
|
|
@ -10,8 +10,8 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
NAME = 'Avatars',
|
||||
AUTHOR = 'SnappyMail',
|
||||
URL = 'https://snappymail.eu/',
|
||||
VERSION = '1.15',
|
||||
RELEASE = '2024-01-22',
|
||||
VERSION = '1.16',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.25.0',
|
||||
CATEGORY = 'Contacts',
|
||||
LICENSE = 'MIT',
|
||||
|
@ -208,7 +208,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
return null;
|
||||
}
|
||||
|
||||
$sAsciiEmail = \mb_strtolower(\MailSo\Base\Utils::IdnToAscii($sEmail, true));
|
||||
$sAsciiEmail = \mb_strtolower(\SnappyMail\IDN::emailToAscii($sEmail));
|
||||
$sEmailId = \sha1($sAsciiEmail);
|
||||
|
||||
\MailSo\Base\Http::setETag($sEmailId);
|
||||
|
@ -310,7 +310,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
private static function cacheImage(string $sEmail, array $aResult) : void
|
||||
{
|
||||
$sEmailId = \sha1(\mb_strtolower(\MailSo\Base\Utils::IdnToAscii($sEmail, true)));
|
||||
$sEmailId = \sha1(\mb_strtolower(\SnappyMail\IDN::emailToAscii($sEmail)));
|
||||
if (!\is_dir(\APP_PRIVATE_DATA . 'avatars')) {
|
||||
\mkdir(\APP_PRIVATE_DATA . 'avatars', 0700);
|
||||
}
|
||||
|
@ -323,7 +323,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
private static function getCachedImage(string $sEmail) : ?array
|
||||
{
|
||||
$sEmail = \mb_strtolower(\MailSo\Base\Utils::IdnToAscii($sEmail, true));
|
||||
$sEmail = \mb_strtolower(\SnappyMail\IDN::emailToAscii($sEmail));
|
||||
$aFiles = \glob(\APP_PRIVATE_DATA . "avatars/{$sEmail}.*");
|
||||
if ($aFiles) {
|
||||
\MailSo\Base\Http::setLastModified(\filemtime($aFiles[0]));
|
||||
|
|
|
@ -57,7 +57,7 @@ class ChangePasswordHMailServerDriver
|
|||
$this->oConfig->Get('plugin', 'hmailserver_password', '')
|
||||
)) {
|
||||
$sEmail = $oAccount->Email();
|
||||
$sDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail);
|
||||
$sDomain = \MailSo\Base\Utils::getEmailAddressDomain($sEmail);
|
||||
$oHmailDomain = $oHmailApp->Domains->ItemByName($sDomain);
|
||||
if ($oHmailDomain) {
|
||||
$oHmailAccount = $oHmailDomain->Accounts->ItemByAddress($sEmail);
|
||||
|
|
|
@ -6,9 +6,9 @@ class ChangePasswordHMailServerPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Change Password hMailServer',
|
||||
VERSION = '2.0',
|
||||
RELEASE = '2022-10-14',
|
||||
REQUIRED = '2.15.3',
|
||||
VERSION = '2.1',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.35.3',
|
||||
CATEGORY = 'Security',
|
||||
DESCRIPTION = 'Extension to allow users to change their passwords through hMailServer';
|
||||
|
||||
|
|
|
@ -57,12 +57,12 @@ class ChangePasswordDriverLDAP
|
|||
|
||||
public function ChangePassword(\RainLoop\Model\Account $oAccount, string $sPrevPassword, string $sNewPassword) : bool
|
||||
{
|
||||
$sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email());
|
||||
$sDomain = \MailSo\Base\Utils::getEmailAddressDomain($oAccount->Email());
|
||||
$sUserDn = \strtr($this->sUserDnFormat, array(
|
||||
'{domain}' => $sDomain,
|
||||
'{domain:dc}' => 'dc='.\strtr($sDomain, array('.' => ',dc=')),
|
||||
'{email}' => $oAccount->Email(),
|
||||
'{email:user}' => \MailSo\Base\Utils::GetAccountNameFromEmail($oAccount->Email()),
|
||||
'{email:user}' => \MailSo\Base\Utils::getEmailAddressLocalPart($oAccount->Email()),
|
||||
'{email:domain}' => $sDomain,
|
||||
'{login}' => $oAccount->IncLogin(),
|
||||
'{imap:login}' => $oAccount->IncLogin(),
|
||||
|
|
|
@ -87,8 +87,8 @@ class ChangePasswordDriverPDO
|
|||
':email' => $sEmail,
|
||||
':oldpass' => $encrypt_prefix . \ChangePasswordPlugin::encrypt($encrypt, $sPrevPassword),
|
||||
':newpass' => $encrypt_prefix . \ChangePasswordPlugin::encrypt($encrypt, $sNewPassword),
|
||||
':domain' => \MailSo\Base\Utils::GetDomainFromEmail($sEmail),
|
||||
':username' => \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail),
|
||||
':domain' => \MailSo\Base\Utils::getEmailAddressDomain($sEmail),
|
||||
':username' => \MailSo\Base\Utils::getEmailAddressLocalPart($sEmail),
|
||||
':login_name' => $oAccount->IncLogin()
|
||||
);
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ class ChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
const
|
||||
NAME = 'Change Password',
|
||||
VERSION = '2.20',
|
||||
RELEASE = '2024-03-10',
|
||||
REQUIRED = '2.23.0',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.35.3',
|
||||
CATEGORY = 'Security',
|
||||
DESCRIPTION = 'Extension to allow users to change their passwords';
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
getEmailAddressDomain<?php
|
||||
|
||||
class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISuggestions
|
||||
{
|
||||
|
@ -93,12 +93,12 @@ class LdapContactsSuggestions implements \RainLoop\Providers\Suggestions\ISugges
|
|||
return $aResult;
|
||||
}
|
||||
|
||||
$sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email());
|
||||
$sDomain = \MailSo\Base\Utils::getEmailAddressDomain($oAccount->Email());
|
||||
$sBaseDn = \strtr($this->sBaseDn, array(
|
||||
'{domain}' => $sDomain,
|
||||
'{domain:dc}' => 'dc='.\strtr($sDomain, array('.' => ',dc=')),
|
||||
'{email}' => $oAccount->Email(),
|
||||
'{email:user}' => \MailSo\Base\Utils::GetAccountNameFromEmail($oAccount->Email()),
|
||||
'{email:user}' => \MailSo\Base\Utils::getEmailAddressLocalPart($oAccount->Email()),
|
||||
'{email:domain}' => $sDomain,
|
||||
'{login}' => $oAccount->IncLogin(),
|
||||
'{imap:login}' => $oAccount->IncLogin(),
|
||||
|
|
|
@ -5,8 +5,8 @@ class LdapContactsSuggestionsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
const
|
||||
NAME = 'Contacts suggestions (LDAP)',
|
||||
VERSION = '2.14',
|
||||
RELEASE = '2024-03-10',
|
||||
REQUIRED = '2.23.0',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.35.3',
|
||||
CATEGORY = 'Contacts',
|
||||
DESCRIPTION = 'Get contacts suggestions from LDAP.';
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@ class LDAPLoginMappingPlugin extends AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'LDAP login mapping',
|
||||
VERSION = '2.1',
|
||||
VERSION = '2.2',
|
||||
AUTHOR = 'RainLoop Team, Ludovic Pouzenc<ludovic@pouzenc.fr>, ZephOne<zephone@protonmail.com>',
|
||||
RELEASE = '2023-01-19',
|
||||
REQUIRED = '2.19.2',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.35.3',
|
||||
CATEGORY = 'Login',
|
||||
DESCRIPTION = 'Enable custom mapping using ldap field';
|
||||
/**
|
||||
|
@ -175,7 +175,7 @@ class LDAPLoginMappingPlugin extends AbstractPlugin
|
|||
'LDAP');
|
||||
return FALSE;
|
||||
}
|
||||
$sLogin = \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail);
|
||||
$sLogin = \MailSo\Base\Utils::getEmailAddressLocalPart($sEmail);
|
||||
|
||||
$this->oLogger->Write('ldap_connect: trying...', \LOG_INFO, 'LDAP');
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ class LdapMailAccounts
|
|||
// and removes the domainname if this was configured inside the domain config.
|
||||
$username = $sEmail;
|
||||
$oActions = \RainLoop\Api::Actions();
|
||||
$oDomain = $oActions->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true);
|
||||
$oDomain = $oActions->DomainProvider()->Load(\MailSo\Base\Utils::getEmailAddressDomain($sEmail), true);
|
||||
if ($oDomain->ImapSettings()->shortLogin){
|
||||
$username = @ldap_escape($this->RemoveEventualDomainPart($sEmail), "", LDAP_ESCAPE_FILTER);
|
||||
}
|
||||
|
@ -415,8 +415,8 @@ class LdapMailAccounts
|
|||
*/
|
||||
public static function RemoveEventualDomainPart(string $sInput) : string
|
||||
{
|
||||
// Copy of \MailSo\Base\Utils::GetAccountNameFromEmail to make sure that also after eventual future
|
||||
// updates the input string gets returned when no '@' is found (GetDomainFromEmail already doesn't do this)
|
||||
// Copy of \MailSo\Base\Utils::getEmailAddressLocalPart to make sure that also after eventual future
|
||||
// updates the input string gets returned when no '@' is found (getEmailAddressDomain already doesn't do this)
|
||||
$sResult = '';
|
||||
if (\strlen($sInput))
|
||||
{
|
||||
|
|
|
@ -15,8 +15,8 @@ class LdapMailAccountsPlugin extends AbstractPlugin
|
|||
VERSION = '2.1.0',
|
||||
AUTHOR = 'cm-schl',
|
||||
URL = 'https://github.com/cm-sch',
|
||||
RELEASE = '2024-03-10',
|
||||
REQUIRED = '2.25.4',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.35.3',
|
||||
CATEGORY = 'Accounts',
|
||||
DESCRIPTION = 'Add additional mail accounts the SnappyMail user has access to by a LDAP query. Basing on the work of FWest98 (https://github.com/FWest98).';
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ class LoginAutoconfigPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
AUTHOR = 'SnappyMail',
|
||||
URL = 'https://snappymail.eu/',
|
||||
VERSION = '1.1',
|
||||
RELEASE = '2024-03-10',
|
||||
REQUIRED = '2.34.0',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.35.3',
|
||||
CATEGORY = 'Login',
|
||||
LICENSE = 'MIT',
|
||||
DESCRIPTION = 'Tries to login using the domain autoconfig';
|
||||
|
@ -27,7 +27,7 @@ class LoginAutoconfigPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
if (\str_contains($sEmail, '@')) {
|
||||
$oProvider = $this->Manager()->Actions()->DomainProvider();
|
||||
$sDomain = \MailSo\Base\Utils::GetDomainFromEmail($sEmail);
|
||||
$sDomain = \MailSo\Base\Utils::getEmailAddressDomain($sEmail);
|
||||
$oDomain = $oProvider->Load($sDomain, false);
|
||||
if (!$oDomain) {
|
||||
$result = \RainLoop\Providers\Domain\Autoconfig::discover($sEmail);
|
||||
|
|
|
@ -4,9 +4,9 @@ class LoginOAuth2Plugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'OAuth2',
|
||||
VERSION = '1.1',
|
||||
RELEASE = '2022-11-11',
|
||||
REQUIRED = '2.21.0',
|
||||
VERSION = '1.2',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.35.3',
|
||||
CATEGORY = 'Login',
|
||||
DESCRIPTION = 'IMAP, Sieve & SMTP login using RFC 7628 OAuth2';
|
||||
|
||||
|
@ -55,7 +55,7 @@ class LoginOAuth2Plugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
public function clientLogin(\RainLoop\Model\Account $oAccount, \MailSo\Net\NetClient $oClient, \MailSo\Net\ConnectSettings $oSettings) : void
|
||||
{
|
||||
$sPassword = $oSettings->Password;
|
||||
$sPassword = $oSettings->passphrase;
|
||||
$iGatLen = \strlen(static::GMAIL_TOKENS_PREFIX);
|
||||
if ($sPassword && static::GMAIL_TOKENS_PREFIX === \substr($sPassword, 0, $iGatLen)) {
|
||||
$aTokens = \json_decode(\substr($sPassword, $iGatLen));
|
||||
|
@ -63,7 +63,7 @@ class LoginOAuth2Plugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
$sRefreshToken = !empty($aTokens[1]) ? $aTokens[1] : '';
|
||||
}
|
||||
if ($sAccessToken && $sRefreshToken) {
|
||||
$oSettings->Password = $this->gmailRefreshToken($sAccessToken, $sRefreshToken);
|
||||
$oSettings->passphrase = $this->gmailRefreshToken($sAccessToken, $sRefreshToken);
|
||||
\array_unshift($oSettings->SASLMechanisms, 'OAUTHBEARER', 'XOAUTH2');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ class LoginOverridePlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Login Override',
|
||||
VERSION = '2.2',
|
||||
RELEASE = '2023-02-08',
|
||||
REQUIRED = '2.25.3',
|
||||
VERSION = '2.3',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.35.3',
|
||||
CATEGORY = 'Filters',
|
||||
DESCRIPTION = 'Override IMAP/SMTP login credentials for specific users.';
|
||||
|
||||
|
@ -50,10 +50,10 @@ class LoginOverridePlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
$line = \explode(':', $line, 3);
|
||||
if (!empty($line[0]) && $line[0] === $sEmail) {
|
||||
if (!empty($line[1])) {
|
||||
$oSettings->Login = $line[1];
|
||||
$oSettings->username = $line[1];
|
||||
}
|
||||
if (!empty($line[2])) {
|
||||
$oSettings->Password = $line[2];
|
||||
$oSettings->passphrase = $line[2];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Nextcloud',
|
||||
VERSION = '2.31',
|
||||
RELEASE = '2024-02-18',
|
||||
VERSION = '2.32',
|
||||
RELEASE = '2024-03-12',
|
||||
CATEGORY = 'Integrations',
|
||||
DESCRIPTION = 'Integrate with Nextcloud v20+',
|
||||
REQUIRED = '2.34.0';
|
||||
REQUIRED = '2.35.3';
|
||||
|
||||
public function Init() : void
|
||||
{
|
||||
|
@ -80,7 +80,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
) {
|
||||
$sAccessToken = \OC::$server->getSession()->get('oidc_access_token');
|
||||
if ($sAccessToken) {
|
||||
$oSettings->Password = $sAccessToken;
|
||||
$oSettings->passphrase = $sAccessToken;
|
||||
\array_unshift($oSettings->SASLMechanisms, 'OAUTHBEARER');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ class OverrideSmtpCredentialsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Override SMTP Credentials',
|
||||
VERSION = '2.4',
|
||||
RELEASE = '2023-01-19',
|
||||
REQUIRED = '2.23.0',
|
||||
VERSION = '2.5',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.35.3',
|
||||
CATEGORY = 'Filters',
|
||||
DESCRIPTION = 'Override SMTP credentials for specific users.';
|
||||
|
||||
|
@ -67,8 +67,8 @@ class OverrideSmtpCredentialsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
$sFoundValue = '';
|
||||
if (\strlen($sWhiteList) && \RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $sWhiteList, $sFoundValue)) {
|
||||
$oSettings->useAuth = (bool) $this->Config()->Get('plugin', 'smtp_auth', true);
|
||||
$oSettings->Login = \trim($this->Config()->Get('plugin', 'smtp_user', ''));
|
||||
$oSettings->Password = (string) $this->Config()->Get('plugin', 'smtp_password', '');
|
||||
$oSettings->username = \trim($this->Config()->Get('plugin', 'smtp_user', ''));
|
||||
$oSettings->passphrase = (string) $this->Config()->Get('plugin', 'smtp_password', '');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
AUTHOR = 'SnappyMail',
|
||||
URL = 'https://snappymail.eu/',
|
||||
VERSION = '2.16',
|
||||
RELEASE = '2024-03-11',
|
||||
REQUIRED = '2.26.4',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.35.3',
|
||||
CATEGORY = 'General',
|
||||
LICENSE = 'MIT',
|
||||
DESCRIPTION = 'A CAPTCHA (v2) is a program that can generate and grade tests that humans can pass but current computer programs cannot. For example, humans can read distorted text as the one shown below, but current computer programs can\'t. More info at https://developers.google.com/recaptcha';
|
||||
|
|
|
@ -7,9 +7,9 @@ class SmtpUseFromAdrAccountPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
NAME = 'Use From-Address-Account for smtp',
|
||||
AUTHOR = 'attike',
|
||||
URL = 'https://github.com/attike',
|
||||
VERSION = '1.1',
|
||||
RELEASE = '2024-03-10',
|
||||
REQUIRED = '2.23.0',
|
||||
VERSION = '1.1',
|
||||
RELEASE = '2024-03-12',
|
||||
REQUIRED = '2.35.3',
|
||||
CATEGORY = 'Filters',
|
||||
DESCRIPTION = 'Set smpt-config and -credentials based on selected from-address-account';
|
||||
|
||||
|
@ -88,9 +88,9 @@ class SmtpUseFromAdrAccountPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
if ( isset($this->aFromAccount[$oAccount->Email()]) ) {
|
||||
$oFromAccount = $this->aFromAccount[$oAccount->Email()];
|
||||
unset($this->aFromAccount[$oAccount->Email()]);
|
||||
$oSettings->Login = $oFromAccount->OutLogin();
|
||||
$oSettings->useAuth = $oFromAccount->Domain()->SmtpSettings()->useAuth;
|
||||
$oSettings->Password = $oFromAccount->IncPassword();
|
||||
$oSettings->username = $oFromAccount->OutLogin();
|
||||
$oSettings->passphrase = $oFromAccount->IncPassword();
|
||||
\SnappyMail\LOG::info(get_class($this),'user/pwd rewrite: '. $oFromAccount->Email());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -409,13 +409,27 @@ abstract class Utils
|
|||
return \trim($sValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getEmailAddressLocalPart
|
||||
*/
|
||||
public static function GetAccountNameFromEmail(string $sEmail) : string
|
||||
{
|
||||
return static::getEmailAddressLocalPart($sEmail);
|
||||
}
|
||||
public static function getEmailAddressLocalPart(string $sEmail) : string
|
||||
{
|
||||
$iPos = \strrpos($sEmail, '@');
|
||||
return (false === $iPos) ? $sEmail : \substr($sEmail, 0, $iPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getEmailAddressDomain
|
||||
*/
|
||||
public static function GetDomainFromEmail(string $sEmail) : string
|
||||
{
|
||||
return static::getEmailAddressDomain($sEmail);
|
||||
}
|
||||
public static function getEmailAddressDomain(string $sEmail) : string
|
||||
{
|
||||
$iPos = \strrpos($sEmail, '@');
|
||||
return (false === $iPos) ? '' : \substr($sEmail, $iPos + 1);
|
||||
|
@ -676,10 +690,12 @@ abstract class Utils
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts xn--du8h.snappymail.eu to 📧.snappymail.eu
|
||||
* @deprecated
|
||||
*/
|
||||
public static function IdnToUtf8(string $sStr) : string
|
||||
{
|
||||
$trace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
|
||||
\trigger_error("Deprecated function IdnToAscii called at {$trace['file']}#{$trace['line']}", \E_USER_DEPRECATED);
|
||||
if (\preg_match('/(^|\.|@)xn--/i', $sStr)) {
|
||||
try
|
||||
{
|
||||
|
@ -691,10 +707,12 @@ abstract class Utils
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts 📧.snappymail.eu to xn--du8h.snappymail.eu
|
||||
* @deprecated
|
||||
*/
|
||||
public static function IdnToAscii(string $sStr, bool $bLowerCase = false) : string
|
||||
{
|
||||
$trace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1];
|
||||
\trigger_error("Deprecated function IdnToAscii called at {$trace['file']}#{$trace['line']}", \E_USER_DEPRECATED);
|
||||
$aParts = \explode('@', $sStr);
|
||||
$sDomain = \array_pop($aParts);
|
||||
if (\strlen($sDomain) && \preg_match('/[^\x20-\x7E]/', $sDomain)) {
|
||||
|
|
|
@ -52,7 +52,7 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
public function Hash() : string
|
||||
{
|
||||
return \md5('ImapClientHash/'.
|
||||
$this->Settings->Login . '@' .
|
||||
$this->Settings->username . '@' .
|
||||
$this->Settings->host . ':' .
|
||||
$this->Settings->port
|
||||
);
|
||||
|
@ -110,8 +110,8 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
return $this;
|
||||
}
|
||||
|
||||
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($oSettings->Login));
|
||||
$sPassword = $oSettings->Password;
|
||||
$sLogin = $oSettings->username;
|
||||
$sPassword = $oSettings->passphrase;
|
||||
|
||||
if (!\strlen($sLogin) || !\strlen($sPassword)) {
|
||||
$this->writeLogException(new \UnexpectedValueException, \LOG_ERR);
|
||||
|
|
|
@ -17,8 +17,14 @@ namespace MailSo\Mime;
|
|||
*/
|
||||
class Email implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* display-name https://datatracker.ietf.org/doc/html/rfc2822#section-3.4
|
||||
*/
|
||||
private string $sDisplayName;
|
||||
|
||||
/**
|
||||
* addr-spec https://datatracker.ietf.org/doc/html/rfc2822#section-3.4.1
|
||||
*/
|
||||
private string $sEmail;
|
||||
|
||||
private string $sDkimStatus = Enumerations\DkimStatus::NONE;
|
||||
|
@ -32,8 +38,7 @@ class Email implements \JsonSerializable
|
|||
throw new \ValueError;
|
||||
}
|
||||
|
||||
$this->sEmail = \MailSo\Base\Utils::IdnToAscii(
|
||||
\MailSo\Base\Utils::Trim($sEmail), true);
|
||||
$this->sEmail = \SnappyMail\IDN::emailToAscii(\MailSo\Base\Utils::Trim($sEmail));
|
||||
|
||||
$this->sDisplayName = \MailSo\Base\Utils::Trim($sDisplayName);
|
||||
}
|
||||
|
@ -156,7 +161,7 @@ class Email implements \JsonSerializable
|
|||
|
||||
public function GetEmail(bool $bIdn = false) : string
|
||||
{
|
||||
return $bIdn ? \MailSo\Base\Utils::IdnToUtf8($this->sEmail) : $this->sEmail;
|
||||
return $bIdn ? \SnappyMail\IDN::emailToUtf8($this->sEmail) : $this->sEmail;
|
||||
}
|
||||
|
||||
public function GetDisplayName() : string
|
||||
|
@ -164,14 +169,14 @@ class Email implements \JsonSerializable
|
|||
return $this->sDisplayName;
|
||||
}
|
||||
|
||||
public function GetAccountName() : string
|
||||
public function getLocalPart() : string
|
||||
{
|
||||
return \MailSo\Base\Utils::GetAccountNameFromEmail($this->GetEmail(false));
|
||||
return \MailSo\Base\Utils::getEmailAddressLocalPart($this->sEmail);
|
||||
}
|
||||
|
||||
public function GetDomain(bool $bIdn = false) : string
|
||||
{
|
||||
return \MailSo\Base\Utils::GetDomainFromEmail($this->GetEmail($bIdn));
|
||||
return \MailSo\Base\Utils::getEmailAddressDomain($this->GetEmail($bIdn));
|
||||
}
|
||||
|
||||
public function SetDkimStatus(string $sDkimStatus)
|
||||
|
|
|
@ -48,8 +48,9 @@ class ConnectSettings implements \JsonSerializable
|
|||
'PLAIN',
|
||||
'LOGIN'
|
||||
];
|
||||
public string $Login = '';
|
||||
private ?SensitiveString $Password = null;
|
||||
|
||||
private string $username = '';
|
||||
private ?SensitiveString $passphrase = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
@ -58,21 +59,27 @@ class ConnectSettings implements \JsonSerializable
|
|||
|
||||
public function __get(string $name)
|
||||
{
|
||||
if ('Password' === $name) {
|
||||
return $this->Password ? $this->Password->getValue() : '';
|
||||
$name = \strtolower($name);
|
||||
if ('passphrase' === $name || 'password' === $name) {
|
||||
return $this->passphrase ? $this->passphrase->getValue() : '';
|
||||
}
|
||||
if ('username' === $name || 'login' === $name) {
|
||||
return $this->username;
|
||||
}
|
||||
}
|
||||
|
||||
public function __set(string $name, $value)
|
||||
{
|
||||
if ('Password' === $name) {
|
||||
$this->Password = \is_string($value) ? new SensitiveString($value) : $value;
|
||||
public function __set(string $name,
|
||||
#[\SensitiveParameter]
|
||||
$value
|
||||
) {
|
||||
$name = \strtolower($name);
|
||||
if ('passphrase' === $name || 'password' === $name) {
|
||||
$this->passphrase = \is_string($value) ? new SensitiveString($value) : $value;
|
||||
}
|
||||
if ('username' === $name || 'login' === $name) {
|
||||
$this->username = \SnappyMail\IDN::emailToAscii($value);
|
||||
// $this->username = \SnappyMail\IDN::emailToAscii(\MailSo\Base\Utils::Trim($value));
|
||||
}
|
||||
}
|
||||
|
||||
public static function Host() : string
|
||||
{
|
||||
return \idn_to_ascii($this->host);
|
||||
}
|
||||
|
||||
public static function fromArray(array $aSettings) : self
|
||||
|
|
|
@ -87,8 +87,8 @@ class SieveClient extends \MailSo\Net\NetClient
|
|||
return $this;
|
||||
}
|
||||
|
||||
$sLogin = $oSettings->Login;
|
||||
$sPassword = $oSettings->Password;
|
||||
$sLogin = $oSettings->username;
|
||||
$sPassword = $oSettings->passphrase;
|
||||
$sLoginAuthKey = '';
|
||||
if (!\strlen($sLogin) || !\strlen($sPassword)) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
namespace MailSo\Smtp;
|
||||
|
||||
use MailSo\Net\Enumerations\ConnectionSecurityType;
|
||||
use SnappyMail\IDN;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
|
@ -127,8 +128,8 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
return $this;
|
||||
}
|
||||
|
||||
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($oSettings->Login));
|
||||
$sPassword = $oSettings->Password;
|
||||
$sLogin = $oSettings->username;
|
||||
$sPassword = $oSettings->passphrase;
|
||||
|
||||
$type = '';
|
||||
foreach ($oSettings->SASLMechanisms as $sasl_type) {
|
||||
|
@ -227,9 +228,13 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function MailFrom(string $sFrom, int $iSizeIfSupported = 0, bool $bDsn = false, bool $bRequireTLS = false) : self
|
||||
{
|
||||
$sFrom = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sFrom), true);
|
||||
|
||||
// $sFrom = IDN::emailToAscii($sFrom);
|
||||
$sCmd = "FROM:<{$sFrom}>";
|
||||
// RFC 6531
|
||||
if ($this->hasCapability('SMTPUTF8')) {
|
||||
// $sFrom = IDN::emailToUtf8($sFrom);
|
||||
// $sCmd = "FROM:<{$sFrom}> SMTPUTF8";
|
||||
}
|
||||
|
||||
if (0 < $iSizeIfSupported && $this->hasCapability('SIZE')) {
|
||||
$sCmd .= ' SIZE='.$iSizeIfSupported;
|
||||
|
@ -243,18 +248,10 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
// RFC 6152
|
||||
if ($this->hasCapability('8BITMIME')) {
|
||||
// $sCmd .= ' BODY=8BITMIME';
|
||||
// RFC 6531
|
||||
if ($this->hasCapability('SMTPUTF8')) {
|
||||
// $sCmd .= ' SMTPUTF8';
|
||||
}
|
||||
}
|
||||
// RFC 3030
|
||||
else if ($this->hasCapability('BINARYMIME')) {
|
||||
// $sCmd .= ' BODY=BINARYMIME';
|
||||
// RFC 6531
|
||||
if ($this->hasCapability('SMTPUTF8')) {
|
||||
// $sCmd .= ' SMTPUTF8';
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 8689
|
||||
|
@ -281,7 +278,7 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
$this->writeLogException(new \MailSo\RuntimeException('No sender reverse path has been supplied'), \LOG_ERR);
|
||||
}
|
||||
|
||||
$sTo = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sTo), true);
|
||||
// $sTo = IDN::emailToAscii($sTo);
|
||||
|
||||
$sCmd = 'TO:<'.$sTo.'>';
|
||||
|
||||
|
@ -413,18 +410,16 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function Vrfy(string $sUser) : self
|
||||
{
|
||||
$sUser = \MailSo\Base\Utils::Trim($sUser);
|
||||
/*
|
||||
// RFC 6531
|
||||
if ($this->hasCapability('SMTPUTF8')) {
|
||||
$this->sendRequestWithCheck('VRFY '
|
||||
. \MailSo\Base\Utils::IdnToUtf8(\MailSo\Base\Utils::Trim($sUser))
|
||||
. ' SMTPUTF8',
|
||||
$this->sendRequestWithCheck('VRFY ' . IDN::emailToUtf8($sUser) . ' SMTPUTF8',
|
||||
array(250, 251, 252),
|
||||
);
|
||||
} else {
|
||||
*/
|
||||
$this->sendRequestWithCheck(
|
||||
'VRFY ' . \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sUser)),
|
||||
$this->sendRequestWithCheck('VRFY ' . IDN::emailToAscii($sUser),
|
||||
array(250, 251, 252)
|
||||
);
|
||||
return $this;
|
||||
|
@ -442,16 +437,14 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
/*
|
||||
public function Expn(string $sUser) : self
|
||||
{
|
||||
$sUser = \MailSo\Base\Utils::Trim($sUser);
|
||||
// RFC 6531
|
||||
if ($this->hasCapability('SMTPUTF8')) {
|
||||
$this->sendRequestWithCheck('EXPN '
|
||||
. \MailSo\Base\Utils::IdnToUtf8(\MailSo\Base\Utils::Trim($sUser))
|
||||
. ' SMTPUTF8',
|
||||
$this->sendRequestWithCheck('EXPN ' . IDN::emailToUtf8($sUser) . ' SMTPUTF8',
|
||||
array(250, 251, 252)
|
||||
);
|
||||
} else {
|
||||
$this->sendRequestWithCheck('EXPN '
|
||||
. \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sUser)),
|
||||
$this->sendRequestWithCheck('EXPN ' . IDN::emailToAscii($sUser),
|
||||
array(250, 251, 252),
|
||||
);
|
||||
return $this;
|
||||
|
|
|
@ -326,10 +326,10 @@ class Actions
|
|||
$sEmail = $oAccount->Email();
|
||||
|
||||
$sLine = \str_replace('{user:email}', $sEmail, $sLine);
|
||||
$sLine = \str_replace('{user:login}', \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $sLine);
|
||||
$sLine = \str_replace('{user:domain}', \MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sLine);
|
||||
$sLine = \str_replace('{user:login}', \MailSo\Base\Utils::getEmailAddressLocalPart($sEmail), $sLine);
|
||||
$sLine = \str_replace('{user:domain}', \MailSo\Base\Utils::getEmailAddressDomain($sEmail), $sLine);
|
||||
$sLine = \str_replace('{user:domain-clear}',
|
||||
\MailSo\Base\Utils::GetClearDomainName(\MailSo\Base\Utils::GetDomainFromEmail($sEmail)),
|
||||
\MailSo\Base\Utils::GetClearDomainName(\MailSo\Base\Utils::getEmailAddressDomain($sEmail)),
|
||||
$sLine);
|
||||
}
|
||||
}
|
||||
|
@ -533,17 +533,17 @@ class Actions
|
|||
return $this->oPlugins;
|
||||
}
|
||||
|
||||
protected function LoggerAuthHelper(?Model\Account $oAccount, string $sLogin, bool $admin = false): void
|
||||
protected function LoggerAuthHelper(?Model\Account $oAccount, string $sLogin = '', bool $admin = false): void
|
||||
{
|
||||
if ($sLogin) {
|
||||
$sHost = $admin ? $this->Http()->GetHost(true, true) : \MailSo\Base\Utils::GetDomainFromEmail($sLogin);
|
||||
$sHost = $admin ? $this->Http()->GetHost(true, true) : \MailSo\Base\Utils::getEmailAddressDomain($sLogin);
|
||||
$aAdditionalParams = array(
|
||||
'{imap:login}' => $sLogin,
|
||||
'{imap:host}' => $sHost,
|
||||
'{smtp:login}' => $sLogin,
|
||||
'{smtp:host}' => $sHost,
|
||||
'{user:email}' => $sLogin,
|
||||
'{user:login}' => $admin ? $sLogin : \MailSo\Base\Utils::GetAccountNameFromEmail($sLogin),
|
||||
'{user:login}' => $admin ? $sLogin : \MailSo\Base\Utils::getEmailAddressLocalPart($sLogin),
|
||||
'{user:domain}' => $sHost,
|
||||
);
|
||||
} else {
|
||||
|
@ -605,43 +605,43 @@ class Actions
|
|||
} else {
|
||||
$oAccount = $this->getAccountFromToken(false);
|
||||
if ($oAccount) {
|
||||
$aResult = \array_merge($aResult, [
|
||||
'Auth' => true,
|
||||
'Email' => \MailSo\Base\Utils::IdnToUtf8($oAccount->Email()),
|
||||
'accountHash' => $oAccount->Hash(),
|
||||
'accountSignMe' => isset($_COOKIE[self::AUTH_SIGN_ME_TOKEN_KEY]),
|
||||
|
||||
'contactsAllowed' => $this->AddressBookProvider($oAccount)->IsActive(),
|
||||
|
||||
'allowSpellcheck' => $oConfig->Get('defaults', 'allow_spellcheck', false),
|
||||
'ViewHTML' => (bool) $oConfig->Get('defaults', 'view_html', true),
|
||||
'ViewImages' => $oConfig->Get('defaults', 'view_images', 'ask'),
|
||||
'ViewImagesWhitelist' => '',
|
||||
'RemoveColors' => (bool) $oConfig->Get('defaults', 'remove_colors', false),
|
||||
'AllowStyles' => false,
|
||||
'ListInlineAttachments' => false,
|
||||
'CollapseBlockquotes' => $oConfig->Get('defaults', 'collapse_blockquotes', true),
|
||||
'MaxBlockquotesLevel' => 0,
|
||||
'simpleAttachmentsList' => false,
|
||||
'listGrouped' => $oConfig->Get('defaults', 'mail_list_grouped', false),
|
||||
'MessagesPerPage' => (int) $oConfig->Get('webmail', 'messages_per_page', 25),
|
||||
'messageNewWindow' => false,
|
||||
'messageReadAuto' => true, // (bool) $oConfig->Get('webmail', 'message_read_auto', true),
|
||||
'MessageReadDelay' => (int) $oConfig->Get('webmail', 'message_read_delay', 5),
|
||||
'MsgDefaultAction' => (int) $oConfig->Get('defaults', 'msg_default_action', 1),
|
||||
'SoundNotification' => true,
|
||||
'NotificationSound' => 'new-mail',
|
||||
'DesktopNotifications' => true,
|
||||
'Layout' => (int) $oConfig->Get('defaults', 'view_layout', Enumerations\Layout::SIDE_PREVIEW),
|
||||
'EditorDefaultType' => \str_replace('Forced', '', $oConfig->Get('defaults', 'view_editor_type', '')),
|
||||
'editorWysiwyg' => 'Squire',
|
||||
'UseCheckboxesInList' => (bool) $oConfig->Get('defaults', 'view_use_checkboxes', true),
|
||||
'showNextMessage' => (bool) $oConfig->Get('defaults', 'view_show_next_message', false),
|
||||
'AutoLogout' => (int) $oConfig->Get('defaults', 'autologout', 30),
|
||||
'AllowDraftAutosave' => (bool) $oConfig->Get('defaults', 'allow_draft_autosave', true),
|
||||
'ContactsAutosave' => (bool) $oConfig->Get('defaults', 'contacts_autosave', true),
|
||||
'sieveAllowFileintoInbox' => (bool)$oConfig->Get('labs', 'sieve_allow_fileinto_inbox', false)
|
||||
]);
|
||||
$aResult = \array_merge(
|
||||
$aResult,
|
||||
[
|
||||
'Auth' => true,
|
||||
'accountSignMe' => isset($_COOKIE[self::AUTH_SIGN_ME_TOKEN_KEY]),
|
||||
'allowSpellcheck' => $oConfig->Get('defaults', 'allow_spellcheck', false),
|
||||
'ViewHTML' => (bool) $oConfig->Get('defaults', 'view_html', true),
|
||||
'ViewImages' => $oConfig->Get('defaults', 'view_images', 'ask'),
|
||||
'ViewImagesWhitelist' => '',
|
||||
'RemoveColors' => (bool) $oConfig->Get('defaults', 'remove_colors', false),
|
||||
'AllowStyles' => false,
|
||||
'ListInlineAttachments' => false,
|
||||
'CollapseBlockquotes' => $oConfig->Get('defaults', 'collapse_blockquotes', true),
|
||||
'MaxBlockquotesLevel' => 0,
|
||||
'simpleAttachmentsList' => false,
|
||||
'listGrouped' => $oConfig->Get('defaults', 'mail_list_grouped', false),
|
||||
'MessagesPerPage' => (int) $oConfig->Get('webmail', 'messages_per_page', 25),
|
||||
'messageNewWindow' => false,
|
||||
'messageReadAuto' => true, // (bool) $oConfig->Get('webmail', 'message_read_auto', true),
|
||||
'MessageReadDelay' => (int) $oConfig->Get('webmail', 'message_read_delay', 5),
|
||||
'MsgDefaultAction' => (int) $oConfig->Get('defaults', 'msg_default_action', 1),
|
||||
'SoundNotification' => true,
|
||||
'NotificationSound' => 'new-mail',
|
||||
'DesktopNotifications' => true,
|
||||
'Layout' => (int) $oConfig->Get('defaults', 'view_layout', Enumerations\Layout::SIDE_PREVIEW),
|
||||
'EditorDefaultType' => \str_replace('Forced', '', $oConfig->Get('defaults', 'view_editor_type', '')),
|
||||
'editorWysiwyg' => 'Squire',
|
||||
'UseCheckboxesInList' => (bool) $oConfig->Get('defaults', 'view_use_checkboxes', true),
|
||||
'showNextMessage' => (bool) $oConfig->Get('defaults', 'view_show_next_message', false),
|
||||
'AutoLogout' => (int) $oConfig->Get('defaults', 'autologout', 30),
|
||||
'AllowDraftAutosave' => (bool) $oConfig->Get('defaults', 'allow_draft_autosave', true),
|
||||
'ContactsAutosave' => (bool) $oConfig->Get('defaults', 'contacts_autosave', true),
|
||||
'sieveAllowFileintoInbox' => (bool)$oConfig->Get('labs', 'sieve_allow_fileinto_inbox', false)
|
||||
],
|
||||
// MainAccount or AdditionalAccount
|
||||
$this->getAccountData($oAccount)
|
||||
);
|
||||
|
||||
$aAttachmentsActions = array();
|
||||
if ($this->GetCapa(Capa::ATTACHMENTS_ACTIONS)) {
|
||||
|
@ -677,13 +677,10 @@ class Actions
|
|||
|
||||
$mMailToData = Utils::DecodeKeyValuesQ($sToken);
|
||||
if (!empty($mMailToData['MailTo']) && 'MailTo' === $mMailToData['MailTo'] && !empty($mMailToData['To'])) {
|
||||
$aResult['mailToEmail'] = \MailSo\Base\Utils::IdnToUtf8($mMailToData['To']);
|
||||
$aResult['mailToEmail'] = \SnappyMail\IDN::emailToUtf8($mMailToData['To']);
|
||||
}
|
||||
}
|
||||
|
||||
// MainAccount or AdditionalAccount
|
||||
$aResult = \array_merge($aResult, $this->getAccountData($oAccount));
|
||||
|
||||
// MainAccount
|
||||
$oSettings = $this->SettingsProvider()->Load($oAccount);
|
||||
if ($oSettings instanceof Settings) {
|
||||
|
|
|
@ -12,6 +12,7 @@ use RainLoop\Notifications;
|
|||
use RainLoop\Providers\Identities;
|
||||
use RainLoop\Providers\Storage\Enumerations\StorageType;
|
||||
use RainLoop\Utils;
|
||||
use SnappyMail\IDN;
|
||||
|
||||
trait Accounts
|
||||
{
|
||||
|
@ -85,7 +86,7 @@ trait Accounts
|
|||
$sName = \trim($this->GetActionParam('name', ''));
|
||||
$bNew = !empty($this->GetActionParam('new', 1));
|
||||
|
||||
$sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
||||
$sEmail = IDN::emailToAscii($sEmail);
|
||||
if ($bNew && ($oMainAccount->Email() === $sEmail || isset($aAccounts[$sEmail]))) {
|
||||
throw new ClientException(Notifications::AccountAlreadyExists);
|
||||
} else if (!$bNew && !isset($aAccounts[$sEmail])) {
|
||||
|
@ -109,7 +110,7 @@ trait Accounts
|
|||
|
||||
protected function loadAdditionalAccountImapClient(string $sEmail): \MailSo\Imap\ImapClient
|
||||
{
|
||||
$sEmail = \MailSo\Base\Utils::IdnToAscii(\trim($sEmail), true);
|
||||
$sEmail = IDN::emailToAscii($sEmail);
|
||||
if (!\strlen($sEmail)) {
|
||||
throw new ClientException(Notifications::AccountDoesNotExist);
|
||||
}
|
||||
|
@ -173,7 +174,7 @@ trait Accounts
|
|||
}
|
||||
|
||||
$sEmailToDelete = \trim($this->GetActionParam('emailToDelete', ''));
|
||||
$sEmailToDelete = \MailSo\Base\Utils::IdnToAscii($sEmailToDelete, true);
|
||||
$sEmailToDelete = IDN::emailToAscii($sEmailToDelete);
|
||||
|
||||
$aAccounts = $this->GetAccounts($oMainAccount);
|
||||
|
||||
|
@ -198,6 +199,7 @@ trait Accounts
|
|||
{
|
||||
$oConfig = $this->Config();
|
||||
$aResult = [
|
||||
// 'Email' => IDN::emailToUtf8($oAccount->Email()),
|
||||
'Email' => $oAccount->Email(),
|
||||
'accountHash' => $oAccount->Hash(),
|
||||
'mainEmail' => \RainLoop\Api::Actions()->getMainAccountFromToken()->Email(),
|
||||
|
@ -330,7 +332,7 @@ trait Accounts
|
|||
return $this->DefaultResponse(array(
|
||||
'Accounts' => \array_values(\array_map(function($value){
|
||||
return [
|
||||
'email' => \MailSo\Base\Utils::IdnToUtf8($value['email'] ?? $value[1]),
|
||||
'email' => IDN::emailToUtf8($value['email'] ?? $value[1]),
|
||||
'name' => $value['name'] ?? ''
|
||||
];
|
||||
},
|
||||
|
|
|
@ -64,7 +64,7 @@ trait AdminDomains
|
|||
$sLogin = '';
|
||||
$this->resolveLoginCredentials($sEmail, $oPassword, $sLogin);
|
||||
$oDomain = \str_contains($sEmail, '@')
|
||||
? $this->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true)
|
||||
? $this->DomainProvider()->Load(\MailSo\Base\Utils::getEmailAddressDomain($sEmail), true)
|
||||
: null;
|
||||
return $this->DefaultResponse(array(
|
||||
'email' => $sEmail,
|
||||
|
@ -111,8 +111,8 @@ trait AdminDomains
|
|||
'connectCapa' => \array_values(\array_diff($oImapClient->Capabilities(), ['STARTTLS']))
|
||||
];
|
||||
if (!empty($aAuth['user'])) {
|
||||
$oSettings->Login = $aAuth['user'];
|
||||
$oSettings->Password = $aAuth['pass'];
|
||||
$oSettings->username = $aAuth['user'];
|
||||
$oSettings->passphrase = $aAuth['pass'];
|
||||
$oImapClient->Login($oSettings);
|
||||
$mImapResult['authCapa'] = \array_values(\array_unique(\array_map(function($n){
|
||||
return \str_starts_with($n, 'THREAD=') ? 'THREAD' : $n;
|
||||
|
@ -156,8 +156,8 @@ trait AdminDomains
|
|||
];
|
||||
|
||||
if (!empty($aAuth['user'])) {
|
||||
$oSettings->Login = $aAuth['user'];
|
||||
$oSettings->Password = $aAuth['pass'];
|
||||
$oSettings->username = $aAuth['user'];
|
||||
$oSettings->passphrase = $aAuth['pass'];
|
||||
$oSmtpClient->Login($oSettings);
|
||||
$mSmtpResult['authCapa'] = $oSmtpClient->Capability();
|
||||
}
|
||||
|
@ -192,8 +192,8 @@ trait AdminDomains
|
|||
];
|
||||
|
||||
if (!empty($aAuth['user'])) {
|
||||
$oSettings->Login = $aAuth['user'];
|
||||
$oSettings->Password = $aAuth['pass'];
|
||||
$oSettings->username = $aAuth['user'];
|
||||
$oSettings->passphrase = $aAuth['pass'];
|
||||
$oSieveClient->Login($oSettings);
|
||||
$mSieveResult['authCapa'] = $oSieveClient->Capability();
|
||||
}
|
||||
|
|
|
@ -762,8 +762,7 @@ trait Messages
|
|||
$this->Plugins()->RunHook('filter.smtp-from', array($oAccount, $oMessage, &$sFrom));
|
||||
|
||||
$aHiddenRcpt = array();
|
||||
if ($bAddHiddenRcpt)
|
||||
{
|
||||
if ($bAddHiddenRcpt) {
|
||||
$this->Plugins()->RunHook('filter.smtp-hidden-rcpt', array($oAccount, $oMessage, &$aHiddenRcpt));
|
||||
}
|
||||
|
||||
|
|
|
@ -154,26 +154,23 @@ trait UserAuth
|
|||
$oMainAccount = $this->getMainAccountFromToken(false);
|
||||
if ($sEmail && $oMainAccount && $this->GetCapa(Capa::ADDITIONAL_ACCOUNTS)) {
|
||||
$oAccount = null;
|
||||
if ($oMainAccount->Email() === $sEmail) {
|
||||
$this->SetAdditionalAuthToken($oAccount);
|
||||
return true;
|
||||
}
|
||||
$sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail);
|
||||
$aAccounts = $this->GetAccounts($oMainAccount);
|
||||
if (!isset($aAccounts[$sEmail])) {
|
||||
throw new ClientException(Notifications::AccountDoesNotExist);
|
||||
}
|
||||
$oAccount = AdditionalAccount::NewInstanceFromTokenArray(
|
||||
$this, $aAccounts[$sEmail]
|
||||
);
|
||||
if (!$oAccount) {
|
||||
throw new ClientException(Notifications::AccountSwitchFailed);
|
||||
}
|
||||
|
||||
// Test the login
|
||||
$oImapClient = new \MailSo\Imap\ImapClient;
|
||||
$this->imapConnect($oAccount, false, $oImapClient);
|
||||
if ($oMainAccount->Email() !== $sEmail) {
|
||||
$sEmail = \SnappyMail\IDN::emailToAscii($sEmail);
|
||||
$aAccounts = $this->GetAccounts($oMainAccount);
|
||||
if (!isset($aAccounts[$sEmail])) {
|
||||
throw new ClientException(Notifications::AccountDoesNotExist);
|
||||
}
|
||||
$oAccount = AdditionalAccount::NewInstanceFromTokenArray(
|
||||
$this, $aAccounts[$sEmail]
|
||||
);
|
||||
if (!$oAccount) {
|
||||
throw new ClientException(Notifications::AccountSwitchFailed);
|
||||
}
|
||||
|
||||
// Test the login
|
||||
$oImapClient = new \MailSo\Imap\ImapClient;
|
||||
$this->imapConnect($oAccount, false, $oImapClient);
|
||||
}
|
||||
$this->SetAdditionalAuthToken($oAccount);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ abstract class Api
|
|||
public static function ClearUserData(string $sEmail) : bool
|
||||
{
|
||||
if (\strlen($sEmail)) {
|
||||
$sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail);
|
||||
$sEmail = \SnappyMail\IDN::emailToAscii($sEmail);
|
||||
|
||||
$oStorageProvider = static::Actions()->StorageProvider();
|
||||
if ($oStorageProvider && $oStorageProvider->IsActive()) {
|
||||
|
|
|
@ -19,7 +19,7 @@ abstract class Account implements \JsonSerializable
|
|||
|
||||
private string $sSmtpLogin = '';
|
||||
|
||||
private ?SensitiveString $sSmtpPassword = null;
|
||||
private ?SensitiveString $oSmtpPassword = null;
|
||||
|
||||
private Domain $oDomain;
|
||||
|
||||
|
@ -36,7 +36,7 @@ abstract class Account implements \JsonSerializable
|
|||
public function IncLogin() : string
|
||||
{
|
||||
return $this->oDomain->ImapSettings()->shortLogin
|
||||
? \MailSo\Base\Utils::GetAccountNameFromEmail($this->sLogin)
|
||||
? \MailSo\Base\Utils::getEmailAddressLocalPart($this->sLogin)
|
||||
: $this->sLogin;
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ abstract class Account implements \JsonSerializable
|
|||
{
|
||||
$sSmtpLogin = $this->sSmtpLogin ?: $this->sLogin;
|
||||
return $this->oDomain->SmtpSettings()->shortLogin
|
||||
? \MailSo\Base\Utils::GetAccountNameFromEmail($sSmtpLogin)
|
||||
? \MailSo\Base\Utils::getEmailAddressLocalPart($sSmtpLogin)
|
||||
: $sSmtpLogin;
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ abstract class Account implements \JsonSerializable
|
|||
string $sPassword
|
||||
) : void
|
||||
{
|
||||
$this->sSmtpPassword = new SensitiveString($sPassword);
|
||||
$this->oSmtpPassword = new SensitiveString($sPassword);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
|
@ -90,10 +90,10 @@ abstract class Account implements \JsonSerializable
|
|||
'pass' => $this->IncPassword(),
|
||||
'name' => $this->sName
|
||||
];
|
||||
if ($this->sSmtpLogin && $this->sSmtpPassword) {
|
||||
if ($this->sSmtpLogin && $this->oSmtpPassword) {
|
||||
$result['smtp'] = [
|
||||
'user' => $this->sSmtpLogin,
|
||||
'pass' => $this->sSmtpPassword->getValue()
|
||||
'pass' => $this->oSmtpPassword->getValue()
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
|
@ -106,13 +106,13 @@ abstract class Account implements \JsonSerializable
|
|||
{
|
||||
$oAccount = null;
|
||||
if ($sEmail && $sLogin && \strlen($oPassword)) {
|
||||
$oDomain = $oActions->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true);
|
||||
$oDomain = $oActions->DomainProvider()->Load(\MailSo\Base\Utils::getEmailAddressDomain($sEmail), true);
|
||||
if ($oDomain) {
|
||||
if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) {
|
||||
$oAccount = new static;
|
||||
|
||||
$oAccount->sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
||||
$oAccount->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin);
|
||||
$oAccount->sEmail = \SnappyMail\IDN::emailToAscii($sEmail);
|
||||
$oAccount->sLogin = \SnappyMail\IDN::emailToAscii($sLogin);
|
||||
$oAccount->SetPassword($oPassword);
|
||||
$oAccount->oDomain = $oDomain;
|
||||
|
||||
|
@ -143,18 +143,11 @@ abstract class Account implements \JsonSerializable
|
|||
if (empty($aAccount[0]) || 'account' != $aAccount[0] || 7 > \count($aAccount)) {
|
||||
return [];
|
||||
}
|
||||
$aResult = [
|
||||
return [
|
||||
'email' => $aAccount[1] ?: '',
|
||||
'login' => $aAccount[2] ?: '',
|
||||
'pass' => $aAccount[3] ?: ''
|
||||
];
|
||||
if ($aAccount[5] && $aAccount[6]) {
|
||||
$aResult['proxy'] = [
|
||||
'user' => $aAccount[5],
|
||||
'pass' => $aAccount[6]
|
||||
];
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
public static function NewInstanceFromTokenArray(
|
||||
|
@ -190,7 +183,7 @@ abstract class Account implements \JsonSerializable
|
|||
{
|
||||
$oSettings = $this->Domain()->ImapSettings();
|
||||
$oSettings->timeout = \max($oSettings->timeout, (int) $oConfig->Get('imap', 'timeout', $oSettings->timeout));
|
||||
$oSettings->Login = $this->IncLogin();
|
||||
$oSettings->username = $this->IncLogin();
|
||||
|
||||
$oSettings->expunge_all_on_delete |= !!$oConfig->Get('imap', 'use_expunge_all_on_delete', false);
|
||||
$oSettings->fast_simple_search = !(!$oSettings->fast_simple_search || !$oConfig->Get('imap', 'message_list_fast_simple_search', true));
|
||||
|
@ -207,14 +200,14 @@ abstract class Account implements \JsonSerializable
|
|||
$oImapClient->Connect($oSettings);
|
||||
$oPlugins->RunHook('imap.after-connect', array($this, $oImapClient, $oSettings));
|
||||
|
||||
$oSettings->Password = $this->oPassword;
|
||||
$oSettings->passphrase = $this->oPassword;
|
||||
return $this->netClientLogin($oImapClient, $oPlugins);
|
||||
}
|
||||
|
||||
public function SmtpConnectAndLogin(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Smtp\SmtpClient $oSmtpClient) : bool
|
||||
{
|
||||
$oSettings = $this->Domain()->SmtpSettings();
|
||||
$oSettings->Login = $this->OutLogin();
|
||||
$oSettings->username = $this->OutLogin();
|
||||
$oSettings->Ehlo = \MailSo\Smtp\SmtpClient::EhloHelper();
|
||||
|
||||
$oSmtpClient->Settings = $oSettings;
|
||||
|
@ -227,18 +220,18 @@ abstract class Account implements \JsonSerializable
|
|||
$oSmtpClient->Connect($oSettings);
|
||||
$oPlugins->RunHook('smtp.after-connect', array($this, $oSmtpClient, $oSettings));
|
||||
/*
|
||||
if ($this->oDomain->OutAskCredentials() && !($this->sSmtpPassword && $this->sSmtpLogin)) {
|
||||
if ($this->oDomain->OutAskCredentials() && !($this->oSmtpPassword && $this->sSmtpLogin)) {
|
||||
throw new RequireCredentialsException
|
||||
}
|
||||
*/
|
||||
$oSettings->Password = $this->sSmtpPassword ?: $this->oPassword;
|
||||
$oSettings->passphrase = $this->oSmtpPassword ?: $this->oPassword;
|
||||
return $this->netClientLogin($oSmtpClient, $oPlugins);
|
||||
}
|
||||
|
||||
public function SieveConnectAndLogin(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Sieve\SieveClient $oSieveClient, \RainLoop\Config\Application $oConfig)
|
||||
{
|
||||
$oSettings = $this->Domain()->SieveSettings();
|
||||
$oSettings->Login = $this->IncLogin();
|
||||
$oSettings->username = $this->IncLogin();
|
||||
|
||||
$oSieveClient->Settings = $oSettings;
|
||||
|
||||
|
@ -246,7 +239,7 @@ abstract class Account implements \JsonSerializable
|
|||
$oSieveClient->Connect($oSettings);
|
||||
$oPlugins->RunHook('sieve.after-connect', array($this, $oSieveClient, $oSettings));
|
||||
|
||||
$oSettings->Password = $this->oPassword;
|
||||
$oSettings->passphrase = $this->oPassword;
|
||||
return $this->netClientLogin($oSieveClient, $oPlugins);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class AdditionalAccount extends Account
|
|||
{
|
||||
public function ParentEmail() : string
|
||||
{
|
||||
return \trim(\MailSo\Base\Utils::IdnToAscii(\RainLoop\Api::Actions()->getMainAccountFromToken()->Email(), true));
|
||||
return \SnappyMail\IDN::emailToAscii(\RainLoop\Api::Actions()->getMainAccountFromToken()->Email());
|
||||
}
|
||||
|
||||
public function Hash() : string
|
||||
|
|
|
@ -108,7 +108,7 @@ class Domain implements \JsonSerializable
|
|||
if ($sW) {
|
||||
$sEmail = \mb_strtolower($sEmail);
|
||||
$sLogin = \mb_strtolower($sLogin);
|
||||
$sUserPart = \MailSo\Base\Utils::GetAccountNameFromEmail($sLogin ?: $sEmail);
|
||||
$sUserPart = \MailSo\Base\Utils::getEmailAddressLocalPart($sLogin ?: $sEmail);
|
||||
$sItem = \strtok($sW, " ;,\n");
|
||||
while (false !== $sItem) {
|
||||
$sItem = \mb_strtolower(\idn_to_ascii(\trim($sItem)));
|
||||
|
@ -192,17 +192,17 @@ class Domain implements \JsonSerializable
|
|||
if (\strlen($sName) && \strlen($aDomain['imap_host'])) {
|
||||
$oDomain = new self($sName);
|
||||
|
||||
$oDomain->IMAP->host = \idn_to_utf8($aDomain['imap_host']);
|
||||
$oDomain->IMAP->host = $aDomain['imap_host'];
|
||||
$oDomain->IMAP->port = (int) $aDomain['imap_port'];
|
||||
$oDomain->IMAP->type = self::StrConnectionSecurityTypeToCons($aDomain['imap_secure'] ?? '');
|
||||
$oDomain->IMAP->shortLogin = !empty($aDomain['imap_short_login']);
|
||||
|
||||
$oDomain->Sieve->enabled = !empty($aDomain['sieve_use']);
|
||||
$oDomain->Sieve->host = \idn_to_utf8($aDomain['sieve_host']);
|
||||
$oDomain->Sieve->host = $aDomain['sieve_host'];
|
||||
$oDomain->Sieve->port = (int) ($aDomain['sieve_port'] ?? 4190);;
|
||||
$oDomain->Sieve->type = self::StrConnectionSecurityTypeToCons($aDomain['sieve_secure'] ?? '');
|
||||
|
||||
$oDomain->SMTP->host = \idn_to_utf8($aDomain['smtp_host']);
|
||||
$oDomain->SMTP->host = $aDomain['smtp_host'];
|
||||
$oDomain->SMTP->port = (int) ($aDomain['smtp_port'] ?? 25);
|
||||
$oDomain->SMTP->type = self::StrConnectionSecurityTypeToCons($aDomain['smtp_secure'] ?? '');
|
||||
$oDomain->SMTP->shortLogin = !empty($aDomain['smtp_short_login']);
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace RainLoop\Model;
|
||||
|
||||
use MailSo\Base\Utils;
|
||||
use SnappyMail\SensitiveString;
|
||||
|
||||
class Identity implements \JsonSerializable
|
||||
|
@ -102,7 +101,7 @@ class Identity implements \JsonSerializable
|
|||
if (!empty($aData['Email'])) {
|
||||
$this->sId = !empty($aData['Id']) ? $aData['Id'] : '';
|
||||
$this->sLabel = isset($aData['Label']) ? $aData['Label'] : '';
|
||||
$this->sEmail = $bJson ? Utils::IdnToAscii($aData['Email'], true) : $aData['Email'];
|
||||
$this->sEmail = $bJson ? \SnappyMail\IDN::emailToAscii($aData['Email']) : $aData['Email'];
|
||||
$this->sName = isset($aData['Name']) ? $aData['Name'] : '';
|
||||
$this->sReplyTo = !empty($aData['ReplyTo']) ? $aData['ReplyTo'] : '';
|
||||
$this->sBcc = !empty($aData['Bcc']) ? $aData['Bcc'] : '';
|
||||
|
@ -146,7 +145,7 @@ class Identity implements \JsonSerializable
|
|||
'@Object' => 'Object/Identity',
|
||||
'id' => $this->sId,
|
||||
'label' => $this->sLabel,
|
||||
'email' => Utils::IdnToUtf8($this->sEmail),
|
||||
'email' => \SnappyMail\IDN::emailToUtf8($this->sEmail),
|
||||
'name' => $this->sName,
|
||||
'replyTo' => $this->sReplyTo,
|
||||
'bcc' => $this->sBcc,
|
||||
|
|
|
@ -1161,7 +1161,7 @@ class PdoAddressBook
|
|||
{
|
||||
static $aCache = array();
|
||||
|
||||
$sEmail = \MailSo\Base\Utils::IdnToAscii(\trim($sEmail), true);
|
||||
$sEmail = \SnappyMail\IDN::emailToAscii(\trim($sEmail));
|
||||
if (empty($sEmail)) {
|
||||
throw new \ValueError('Empty Email argument');
|
||||
}
|
||||
|
|
|
@ -9,8 +9,9 @@ abstract class Autoconfig
|
|||
{
|
||||
public static function discover(string $emailaddress) : ?array
|
||||
{
|
||||
$domain = \MailSo\Base\Utils::GetDomainFromEmail($emailaddress);
|
||||
// $domain = \strtolower(\idn_to_ascii($domain));
|
||||
// $emailaddress = \SnappyMail\IDN::emailToAscii($emailaddress);
|
||||
$domain = \MailSo\Base\Utils::getEmailAddressDomain($emailaddress);
|
||||
$domain = \strtolower(\idn_to_ascii($domain));
|
||||
// First try
|
||||
$autoconfig = static::resolve($domain, $emailaddress);
|
||||
if ($autoconfig) {
|
||||
|
|
|
@ -207,23 +207,20 @@ class DefaultDomain implements DomainInterface
|
|||
if ($bAlias) {
|
||||
if ($bIncludeAliases) {
|
||||
$aAliases[$sName] = array(
|
||||
'name' => \idn_to_utf8($sName),
|
||||
'punycode' => $sName,
|
||||
'name' => $sName,
|
||||
'disabled' => \in_array($sName, $aDisabledNames),
|
||||
'alias' => true
|
||||
);
|
||||
}
|
||||
} else if (false !== \strpos($sName, '*')) {
|
||||
$aWildCards[$sName] = array(
|
||||
'name' => \idn_to_utf8($sName),
|
||||
'punycode' => $sName,
|
||||
'name' => $sName,
|
||||
'disabled' => \in_array($sName, $aDisabledNames),
|
||||
'alias' => false
|
||||
);
|
||||
} else {
|
||||
$aResult[$sName] = array(
|
||||
'name' => \idn_to_utf8($sName),
|
||||
'punycode' => $sName,
|
||||
'name' => $sName,
|
||||
'disabled' => \in_array($sName, $aDisabledNames),
|
||||
'alias' => false
|
||||
);
|
||||
|
|
|
@ -13,7 +13,7 @@ abstract class IDN
|
|||
return static::uri($string, true);
|
||||
}
|
||||
if (\str_contains($string, '@')) {
|
||||
return static::emailAddress($string, true);
|
||||
return static::emailToAscii($string);
|
||||
}
|
||||
return \idn_to_ascii($string);
|
||||
}
|
||||
|
@ -24,21 +24,35 @@ abstract class IDN
|
|||
return static::uri($string, false);
|
||||
}
|
||||
if (\str_contains($string, '@')) {
|
||||
return static::emailAddress($string, false);
|
||||
return static::emailToUtf8($string);
|
||||
}
|
||||
return \idn_to_utf8($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts IDN domain part to lowercased punycode
|
||||
* Like: 'Smile😀@📧.SnappyMail.eu' to 'Smile😀@xn--du8h.snappymail.eu'
|
||||
* When the '@' is missing, it does nothing
|
||||
*/
|
||||
public static function emailToAscii(string $address) : string
|
||||
{
|
||||
return static::emailAddress($address, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts IDN domain part to unicode
|
||||
* Like: 'Smile😀@xn--du8h.SnappyMail.eu' to 'Smile😀@📧.SnappyMail.eu'
|
||||
* When the '@' is missing, it does nothing
|
||||
*/
|
||||
public static function emailToUtf8(string $address) : string
|
||||
{
|
||||
return static::emailAddress($address, false);
|
||||
}
|
||||
|
||||
private static function domain(string $domain, bool $toAscii) : string
|
||||
{
|
||||
// if ($toAscii && \preg_match('/[^\x20-\x7E]/', $domain)) {
|
||||
// if (!$toAscii && \preg_match('/(^|\\.)xn--/i', $domain)) {
|
||||
return $toAscii ? \strtolower(\idn_to_ascii($domain)) : \idn_to_utf8($domain);
|
||||
/*
|
||||
$domain = \explode('.', $domain);
|
||||
|
@ -53,7 +67,8 @@ abstract class IDN
|
|||
private static function emailAddress(string $address, bool $toAscii) : string
|
||||
{
|
||||
if (!\str_contains($address, '@')) {
|
||||
throw new \RuntimeException("Invalid email address: {$address}");
|
||||
// throw new \RuntimeException("Invalid email address: {$address}");
|
||||
return $address;
|
||||
}
|
||||
$local = \explode('@', $address);
|
||||
$domain = static::domain(\array_pop($local), $toAscii);
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
<!-- ko foreach: accounts -->
|
||||
<li role="presentation">
|
||||
<a class="email-title" href="#" data-bind="click: $root.accountClick, text: name || email,
|
||||
<a class="email-title" href="#" data-bind="click: $root.accountClick, text: label(),
|
||||
attr: {title: email, 'data-unread': unreadEmails, 'data-icon': $root.accountEmail() === email ? '✔' : '👤'}"></a>
|
||||
</li>
|
||||
<!-- /ko -->
|
||||
|
|
|
@ -64,6 +64,7 @@ config.paths.js = {
|
|||
'vendors/knockout/build/output/knockout-latest.js',
|
||||
// 'vendors/knockout/build/output/knockout-latest.debug.js',
|
||||
'vendors/squire/build/squire-raw.js',
|
||||
'vendors/mathiasbynens/punycode.js',
|
||||
'dev/External/SquireUI.js'
|
||||
]
|
||||
},
|
||||
|
|
395
vendors/mathiasbynens/punycode.js
vendored
Normal file
395
vendors/mathiasbynens/punycode.js
vendored
Normal file
|
@ -0,0 +1,395 @@
|
|||
/**
|
||||
* Modified version of https://github.com/mathiasbynens/punycode.js
|
||||
*/
|
||||
|
||||
(() => {
|
||||
|
||||
'use strict';
|
||||
|
||||
const
|
||||
/** Highest positive signed 32-bit float value */
|
||||
maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
|
||||
|
||||
/** Bootstring parameters */
|
||||
base = 36,
|
||||
tMin = 1,
|
||||
tMax = 26,
|
||||
skew = 38,
|
||||
damp = 700,
|
||||
initialBias = 72,
|
||||
initialN = 128, // 0x80
|
||||
delimiter = '-', // '\x2D'
|
||||
|
||||
/** Regular expressions */
|
||||
regexPunycode = /^xn--/,
|
||||
regexNonASCII = /[^\0-\x7F]/, // Note: U+007F DEL is excluded too.
|
||||
regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators
|
||||
|
||||
/** Error messages */
|
||||
errors = {
|
||||
'overflow': 'Overflow: input needs wider integers to process',
|
||||
'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
|
||||
'invalid-input': 'Invalid input'
|
||||
},
|
||||
|
||||
/** Convenience shortcuts */
|
||||
baseMinusTMin = base - tMin,
|
||||
floor = Math.floor,
|
||||
stringFromCharCode = String.fromCharCode,
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* A generic error utility function.
|
||||
* @private
|
||||
* @param {String} type The error type.
|
||||
* @returns {Error} Throws a `RangeError` with the applicable error message.
|
||||
*/
|
||||
error = type => {
|
||||
throw new RangeError(errors[type])
|
||||
},
|
||||
|
||||
/**
|
||||
* A simple `Array#map`-like wrapper to work with domain name strings or email
|
||||
* addresses.
|
||||
* @private
|
||||
* @param {String} domain The domain name or email address.
|
||||
* @param {Function} callback The function that gets called for every
|
||||
* character.
|
||||
* @returns {String} A new string of characters returned by the callback
|
||||
* function.
|
||||
*/
|
||||
mapDomain = (domain, callback) => {
|
||||
// In email addresses, only the domain name should be punycoded.
|
||||
// Leave the local part (i.e. everything up to `@`) intact.
|
||||
const parts = domain.split('@');
|
||||
parts.push(
|
||||
parts.pop()
|
||||
.split(regexSeparators)
|
||||
.map(label => callback(label))
|
||||
.join('.')
|
||||
);
|
||||
return parts.join('@');
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates an array containing the numeric code points of each Unicode
|
||||
* character in the string. While JavaScript uses UCS-2 internally,
|
||||
* this function will convert a pair of surrogate halves (each of which
|
||||
* UCS-2 exposes as separate characters) into a single code point,
|
||||
* matching UTF-16.
|
||||
* @see <https://mathiasbynens.be/notes/javascript-encoding>
|
||||
* @name decode
|
||||
* @param {String} string The Unicode input string (UCS-2).
|
||||
* @returns {Array} The new array of code points.
|
||||
*/
|
||||
ucs2decode = string => {
|
||||
const output = [];
|
||||
let counter = 0;
|
||||
const length = string.length;
|
||||
while (counter < length) {
|
||||
const value = string.charCodeAt(counter++);
|
||||
if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
|
||||
// It's a high surrogate, and there is a next character.
|
||||
const extra = string.charCodeAt(counter++);
|
||||
if ((extra & 0xFC00) == 0xDC00) { // Low surrogate.
|
||||
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
|
||||
} else {
|
||||
// It's an unmatched surrogate; only append this code unit, in case the
|
||||
// next code unit is the high surrogate of a surrogate pair.
|
||||
output.push(value);
|
||||
counter--;
|
||||
}
|
||||
} else {
|
||||
output.push(value);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a basic code point into a digit/integer.
|
||||
* @see `digitToBasic()`
|
||||
* @private
|
||||
* @param {Number} codePoint The basic numeric code point value.
|
||||
* @returns {Number} The numeric value of a basic code point (for use in
|
||||
* representing integers) in the range `0` to `base - 1`, or `base` if
|
||||
* the code point does not represent a value.
|
||||
*/
|
||||
basicToDigit = codePoint => {
|
||||
if (codePoint >= 0x30 && codePoint < 0x3A) {
|
||||
return 26 + (codePoint - 0x30);
|
||||
}
|
||||
if (codePoint >= 0x41 && codePoint < 0x5B) {
|
||||
return codePoint - 0x41;
|
||||
}
|
||||
if (codePoint >= 0x61 && codePoint < 0x7B) {
|
||||
return codePoint - 0x61;
|
||||
}
|
||||
return base;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a digit/integer into a basic code point.
|
||||
* @see `basicToDigit()`
|
||||
* @private
|
||||
* @param {Number} digit The numeric value of a basic code point.
|
||||
* @returns {Number} The basic code point whose value (when used for
|
||||
* representing integers) is `digit`, which needs to be in the range
|
||||
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
|
||||
* used; else, the lowercase form is used. The behavior is undefined
|
||||
* if `flag` is non-zero and `digit` has no uppercase form.
|
||||
*/
|
||||
digitToBasic = (digit, flag) =>
|
||||
// 0..25 map to ASCII a..z or A..Z
|
||||
// 26..35 map to ASCII 0..9
|
||||
digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5),
|
||||
|
||||
/**
|
||||
* Bias adaptation function as per section 3.4 of RFC 3492.
|
||||
* https://tools.ietf.org/html/rfc3492#section-3.4
|
||||
* @private
|
||||
*/
|
||||
adapt = (delta, numPoints, firstTime) => {
|
||||
let k = 0;
|
||||
delta = firstTime ? floor(delta / damp) : delta >> 1;
|
||||
delta += floor(delta / numPoints);
|
||||
for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
|
||||
delta = floor(delta / baseMinusTMin);
|
||||
}
|
||||
return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a Punycode string of ASCII-only symbols to a string of Unicode
|
||||
* symbols.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The Punycode string of ASCII-only symbols.
|
||||
* @returns {String} The resulting string of Unicode symbols.
|
||||
*/
|
||||
decode = input => {
|
||||
// Don't use UCS-2.
|
||||
const output = [];
|
||||
const inputLength = input.length;
|
||||
let i = 0;
|
||||
let n = initialN;
|
||||
let bias = initialBias;
|
||||
|
||||
// Handle the basic code points: let `basic` be the number of input code
|
||||
// points before the last delimiter, or `0` if there is none, then copy
|
||||
// the first basic code points to the output.
|
||||
|
||||
let basic = input.lastIndexOf(delimiter);
|
||||
if (basic < 0) {
|
||||
basic = 0;
|
||||
}
|
||||
|
||||
for (let j = 0; j < basic; ++j) {
|
||||
// if it's not a basic code point
|
||||
if (input.charCodeAt(j) >= 0x80) {
|
||||
error('not-basic');
|
||||
}
|
||||
output.push(input.charCodeAt(j));
|
||||
}
|
||||
|
||||
// Main decoding loop: start just after the last delimiter if any basic code
|
||||
// points were copied; start at the beginning otherwise.
|
||||
|
||||
for (let index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
|
||||
|
||||
// `index` is the index of the next character to be consumed.
|
||||
// Decode a generalized variable-length integer into `delta`,
|
||||
// which gets added to `i`. The overflow checking is easier
|
||||
// if we increase `i` as we go, then subtract off its starting
|
||||
// value at the end to obtain `delta`.
|
||||
const oldi = i;
|
||||
for (let w = 1, k = base; /* no condition */; k += base) {
|
||||
|
||||
if (index >= inputLength) {
|
||||
error('invalid-input');
|
||||
}
|
||||
|
||||
const digit = basicToDigit(input.charCodeAt(index++));
|
||||
|
||||
if (digit >= base) {
|
||||
error('invalid-input');
|
||||
}
|
||||
if (digit > floor((maxInt - i) / w)) {
|
||||
error('overflow');
|
||||
}
|
||||
|
||||
i += digit * w;
|
||||
const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
|
||||
|
||||
if (digit < t) {
|
||||
break;
|
||||
}
|
||||
|
||||
const baseMinusT = base - t;
|
||||
if (w > floor(maxInt / baseMinusT)) {
|
||||
error('overflow');
|
||||
}
|
||||
|
||||
w *= baseMinusT;
|
||||
|
||||
}
|
||||
|
||||
const out = output.length + 1;
|
||||
bias = adapt(i - oldi, out, oldi == 0);
|
||||
|
||||
// `i` was supposed to wrap around from `out` to `0`,
|
||||
// incrementing `n` each time, so we'll fix that now:
|
||||
if (floor(i / out) > maxInt - n) {
|
||||
error('overflow');
|
||||
}
|
||||
|
||||
n += floor(i / out);
|
||||
i %= out;
|
||||
|
||||
// Insert `n` at position `i` of the output.
|
||||
output.splice(i++, 0, n);
|
||||
|
||||
}
|
||||
|
||||
return String.fromCodePoint(...output);
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a string of Unicode symbols (e.g. a domain name label) to a
|
||||
* Punycode string of ASCII-only symbols.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The string of Unicode symbols.
|
||||
* @returns {String} The resulting Punycode string of ASCII-only symbols.
|
||||
*/
|
||||
encode = input => {
|
||||
const output = [];
|
||||
|
||||
// Convert the input in UCS-2 to an array of Unicode code points.
|
||||
input = ucs2decode(input);
|
||||
|
||||
// Cache the length.
|
||||
const inputLength = input.length;
|
||||
|
||||
// Initialize the state.
|
||||
let n = initialN;
|
||||
let delta = 0;
|
||||
let bias = initialBias;
|
||||
|
||||
// Handle the basic code points.
|
||||
for (const currentValue of input) {
|
||||
if (currentValue < 0x80) {
|
||||
output.push(stringFromCharCode(currentValue));
|
||||
}
|
||||
}
|
||||
|
||||
const basicLength = output.length;
|
||||
let handledCPCount = basicLength;
|
||||
|
||||
// `handledCPCount` is the number of code points that have been handled;
|
||||
// `basicLength` is the number of basic code points.
|
||||
|
||||
// Finish the basic string with a delimiter unless it's empty.
|
||||
if (basicLength) {
|
||||
output.push(delimiter);
|
||||
}
|
||||
|
||||
// Main encoding loop:
|
||||
while (handledCPCount < inputLength) {
|
||||
|
||||
// All non-basic code points < n have been handled already. Find the next
|
||||
// larger one:
|
||||
let m = maxInt;
|
||||
for (const currentValue of input) {
|
||||
if (currentValue >= n && currentValue < m) {
|
||||
m = currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
|
||||
// but guard against overflow.
|
||||
const handledCPCountPlusOne = handledCPCount + 1;
|
||||
if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
|
||||
error('overflow');
|
||||
}
|
||||
|
||||
delta += (m - n) * handledCPCountPlusOne;
|
||||
n = m;
|
||||
|
||||
for (const currentValue of input) {
|
||||
if (currentValue < n && ++delta > maxInt) {
|
||||
error('overflow');
|
||||
}
|
||||
if (currentValue === n) {
|
||||
// Represent delta as a generalized variable-length integer.
|
||||
let q = delta;
|
||||
for (let k = base; /* no condition */; k += base) {
|
||||
const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
|
||||
if (q < t) {
|
||||
break;
|
||||
}
|
||||
const qMinusT = q - t;
|
||||
const baseMinusT = base - t;
|
||||
output.push(
|
||||
stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
|
||||
);
|
||||
q = floor(qMinusT / baseMinusT);
|
||||
}
|
||||
|
||||
output.push(stringFromCharCode(digitToBasic(q, 0)));
|
||||
bias = adapt(delta, handledCPCountPlusOne, handledCPCount === basicLength);
|
||||
delta = 0;
|
||||
++handledCPCount;
|
||||
}
|
||||
}
|
||||
|
||||
++delta;
|
||||
++n;
|
||||
|
||||
}
|
||||
return output.join('');
|
||||
};
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
/** Define the public API */
|
||||
window.IDN = {
|
||||
/**
|
||||
* A string representing the current Punycode.js version number.
|
||||
* @memberOf punycode
|
||||
* @type String
|
||||
*/
|
||||
version: '2.3.1',
|
||||
|
||||
/**
|
||||
* Converts a Punycode string representing a domain name or an email address
|
||||
* to Unicode. Only the Punycoded parts of the input will be converted, i.e.
|
||||
* it doesn't matter if you call it on a string that has already been
|
||||
* converted to Unicode.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The Punycoded domain name or email address to
|
||||
* convert to Unicode.
|
||||
* @returns {String} The Unicode representation of the given Punycode
|
||||
* string.
|
||||
*/
|
||||
toUnicode: input => mapDomain(
|
||||
input,
|
||||
string => regexPunycode.test(string) ? decode(string.slice(4).toLowerCase()) : string
|
||||
),
|
||||
|
||||
/**
|
||||
* Converts a Unicode string representing a domain name or an email address to
|
||||
* Punycode. Only the non-ASCII parts of the domain name will be converted,
|
||||
* i.e. it doesn't matter if you call it with a domain that's already in
|
||||
* ASCII.
|
||||
* @memberOf punycode
|
||||
* @param {String} input The domain name or email address to convert, as a
|
||||
* Unicode string.
|
||||
* @returns {String} The Punycode representation of the given domain name or
|
||||
* email address.
|
||||
*/
|
||||
toASCII: input => mapDomain(
|
||||
input,
|
||||
string => regexNonASCII.test(string) ? 'xn--' + encode(string) : string
|
||||
)
|
||||
};
|
||||
})();
|
Loading…
Reference in a new issue