Improved handling of Internationalized Domain Names in punycode

This commit is contained in:
the-djmaze 2024-03-12 16:06:17 +01:00
parent fd54796710
commit 696a2bbd3c
45 changed files with 661 additions and 236 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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'] ?? ''
];
},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,

View file

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

View file

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

View file

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

View file

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

View file

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