From 696a2bbd3ccdd3652c19aca5863e6736ab27e6eb Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 12 Mar 2024 16:06:17 +0100 Subject: [PATCH] Improved handling of Internationalized Domain Names in punycode --- .eslintrc.js | 4 +- dev/Model/Account.js | 4 + dev/Stores/Admin/Domain.js | 1 + dev/View/User/SystemDropDown.js | 5 +- plugins/avatars/index.php | 10 +- .../change-password-hmailserver/driver.php | 2 +- plugins/change-password-hmailserver/index.php | 6 +- plugins/change-password/drivers/ldap.php | 4 +- plugins/change-password/drivers/pdo.php | 4 +- plugins/change-password/index.php | 4 +- .../LdapContactsSuggestions.php | 6 +- plugins/ldap-contacts-suggestions/index.php | 4 +- plugins/ldap-login-mapping/index.php | 8 +- .../ldap-mail-accounts/LdapMailAccounts.php | 6 +- plugins/ldap-mail-accounts/index.php | 4 +- plugins/login-autoconfig/index.php | 6 +- plugins/login-oauth2/index.php | 10 +- plugins/login-override/index.php | 10 +- plugins/nextcloud/index.php | 8 +- plugins/override-smtp-credentials/index.php | 10 +- plugins/recaptcha/index.php | 4 +- plugins/smtp-use-from-adr-account/index.php | 10 +- .../0.0.0/app/libraries/MailSo/Base/Utils.php | 22 +- .../app/libraries/MailSo/Imap/ImapClient.php | 6 +- .../0.0.0/app/libraries/MailSo/Mime/Email.php | 17 +- .../libraries/MailSo/Net/ConnectSettings.php | 33 +- .../libraries/MailSo/Sieve/SieveClient.php | 4 +- .../app/libraries/MailSo/Smtp/SmtpClient.php | 39 +- .../0.0.0/app/libraries/RainLoop/Actions.php | 91 ++-- .../libraries/RainLoop/Actions/Accounts.php | 10 +- .../RainLoop/Actions/AdminDomains.php | 14 +- .../libraries/RainLoop/Actions/Messages.php | 3 +- .../libraries/RainLoop/Actions/UserAuth.php | 35 +- .../v/0.0.0/app/libraries/RainLoop/Api.php | 2 +- .../app/libraries/RainLoop/Model/Account.php | 41 +- .../RainLoop/Model/AdditionalAccount.php | 2 +- .../app/libraries/RainLoop/Model/Domain.php | 8 +- .../app/libraries/RainLoop/Model/Identity.php | 5 +- .../Providers/AddressBook/PdoAddressBook.php | 2 +- .../RainLoop/Providers/Domain/Autoconfig.php | 5 +- .../Providers/Domain/DefaultDomain.php | 9 +- .../v/0.0.0/app/libraries/snappymail/idn.php | 21 +- .../templates/Views/User/SystemDropDown.html | 2 +- tasks/config.js | 1 + vendors/mathiasbynens/punycode.js | 395 ++++++++++++++++++ 45 files changed, 661 insertions(+), 236 deletions(-) create mode 100644 vendors/mathiasbynens/punycode.js diff --git a/.eslintrc.js b/.eslintrc.js index 6c8cbc1ce..5e2334f0a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -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: { diff --git a/dev/Model/Account.js b/dev/Model/Account.js index d5bc71296..2df0ea725 100644 --- a/dev/Model/Account.js +++ b/dev/Model/Account.js @@ -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 */ diff --git a/dev/Stores/Admin/Domain.js b/dev/Stores/Admin/Domain.js index abd599491..ba09daeab 100644 --- a/dev/Stores/Admin/Domain.js +++ b/dev/Stores/Admin/Domain.js @@ -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; diff --git a/dev/View/User/SystemDropDown.js b/dev/View/User/SystemDropDown.js index 102d5197c..1b10b4fcd 100644 --- a/dev/View/User/SystemDropDown.js +++ b/dev/View/User/SystemDropDown.js @@ -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() { diff --git a/plugins/avatars/index.php b/plugins/avatars/index.php index 9dfcec75d..a43400e54 100644 --- a/plugins/avatars/index.php +++ b/plugins/avatars/index.php @@ -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])); diff --git a/plugins/change-password-hmailserver/driver.php b/plugins/change-password-hmailserver/driver.php index b0e30a6d5..7f6a2c478 100644 --- a/plugins/change-password-hmailserver/driver.php +++ b/plugins/change-password-hmailserver/driver.php @@ -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); diff --git a/plugins/change-password-hmailserver/index.php b/plugins/change-password-hmailserver/index.php index eb8e1cd05..12b28f06c 100644 --- a/plugins/change-password-hmailserver/index.php +++ b/plugins/change-password-hmailserver/index.php @@ -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'; diff --git a/plugins/change-password/drivers/ldap.php b/plugins/change-password/drivers/ldap.php index 73d8b5360..836687311 100644 --- a/plugins/change-password/drivers/ldap.php +++ b/plugins/change-password/drivers/ldap.php @@ -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(), diff --git a/plugins/change-password/drivers/pdo.php b/plugins/change-password/drivers/pdo.php index 634085188..4c744a505 100644 --- a/plugins/change-password/drivers/pdo.php +++ b/plugins/change-password/drivers/pdo.php @@ -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() ); diff --git a/plugins/change-password/index.php b/plugins/change-password/index.php index 89294188b..f33ce4dfe 100644 --- a/plugins/change-password/index.php +++ b/plugins/change-password/index.php @@ -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'; diff --git a/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php b/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php index 040c21156..24ccf750f 100644 --- a/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php +++ b/plugins/ldap-contacts-suggestions/LdapContactsSuggestions.php @@ -1,4 +1,4 @@ -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(), diff --git a/plugins/ldap-contacts-suggestions/index.php b/plugins/ldap-contacts-suggestions/index.php index 01dbab362..dbbf2d515 100644 --- a/plugins/ldap-contacts-suggestions/index.php +++ b/plugins/ldap-contacts-suggestions/index.php @@ -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.'; diff --git a/plugins/ldap-login-mapping/index.php b/plugins/ldap-login-mapping/index.php index 53c8f5622..c15347cab 100644 --- a/plugins/ldap-login-mapping/index.php +++ b/plugins/ldap-login-mapping/index.php @@ -8,10 +8,10 @@ class LDAPLoginMappingPlugin extends AbstractPlugin { const NAME = 'LDAP login mapping', - VERSION = '2.1', + VERSION = '2.2', AUTHOR = 'RainLoop Team, Ludovic Pouzenc, ZephOne', - 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'); diff --git a/plugins/ldap-mail-accounts/LdapMailAccounts.php b/plugins/ldap-mail-accounts/LdapMailAccounts.php index bba3cf29e..3e0378ea2 100644 --- a/plugins/ldap-mail-accounts/LdapMailAccounts.php +++ b/plugins/ldap-mail-accounts/LdapMailAccounts.php @@ -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)) { diff --git a/plugins/ldap-mail-accounts/index.php b/plugins/ldap-mail-accounts/index.php index 19010b046..01118cc0f 100644 --- a/plugins/ldap-mail-accounts/index.php +++ b/plugins/ldap-mail-accounts/index.php @@ -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).'; diff --git a/plugins/login-autoconfig/index.php b/plugins/login-autoconfig/index.php index 30f603eb1..ba23a9b92 100644 --- a/plugins/login-autoconfig/index.php +++ b/plugins/login-autoconfig/index.php @@ -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); diff --git a/plugins/login-oauth2/index.php b/plugins/login-oauth2/index.php index e4e58fe13..133330a6e 100644 --- a/plugins/login-oauth2/index.php +++ b/plugins/login-oauth2/index.php @@ -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'); } } diff --git a/plugins/login-override/index.php b/plugins/login-override/index.php index 8a2110551..26f95d01a 100644 --- a/plugins/login-override/index.php +++ b/plugins/login-override/index.php @@ -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; } diff --git a/plugins/nextcloud/index.php b/plugins/nextcloud/index.php index d17aaa0ed..9ef436aeb 100644 --- a/plugins/nextcloud/index.php +++ b/plugins/nextcloud/index.php @@ -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'); } } diff --git a/plugins/override-smtp-credentials/index.php b/plugins/override-smtp-credentials/index.php index 063d10508..e524bef71 100644 --- a/plugins/override-smtp-credentials/index.php +++ b/plugins/override-smtp-credentials/index.php @@ -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', ''); } } diff --git a/plugins/recaptcha/index.php b/plugins/recaptcha/index.php index e00de534a..9245bd512 100644 --- a/plugins/recaptcha/index.php +++ b/plugins/recaptcha/index.php @@ -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'; diff --git a/plugins/smtp-use-from-adr-account/index.php b/plugins/smtp-use-from-adr-account/index.php index c77f8a628..cea9eab3b 100644 --- a/plugins/smtp-use-from-adr-account/index.php +++ b/plugins/smtp-use-from-adr-account/index.php @@ -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()); } } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php index 133c6b57c..fbb017e22 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php @@ -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)) { diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php index 80b9332c7..837550759 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php @@ -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); diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Email.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Email.php index 66371f9c4..d254459a0 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Email.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Email.php @@ -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) diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Net/ConnectSettings.php b/snappymail/v/0.0.0/app/libraries/MailSo/Net/ConnectSettings.php index b1169fc41..527eba64f 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Net/ConnectSettings.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Net/ConnectSettings.php @@ -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 diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/SieveClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/SieveClient.php index 669273225..35917fd24 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/SieveClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/SieveClient.php @@ -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); diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Smtp/SmtpClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Smtp/SmtpClient.php index 0e3261cbb..f72de9d6c 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Smtp/SmtpClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Smtp/SmtpClient.php @@ -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; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php index 3b804cdcb..0c38b4cc6 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -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) { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php index 1fd3d2003..1eb62f8aa 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php @@ -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'] ?? '' ]; }, diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php index e94769468..82efe716d 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php @@ -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(); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php index 1f178da63..c6d10b451 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php @@ -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)); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php index 8850a52d0..421ec5336 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php @@ -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; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Api.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Api.php index f1d3de2fc..5098824bb 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Api.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Api.php @@ -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()) { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php index fd67ed890..75bfddbcd 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php @@ -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); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/AdditionalAccount.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/AdditionalAccount.php index 764e4cca8..6515025bc 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/AdditionalAccount.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/AdditionalAccount.php @@ -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 diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Domain.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Domain.php index 77e2b6060..cd0000fce 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Domain.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Domain.php @@ -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']); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Identity.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Identity.php index 3cbd725ee..c18f13e0d 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Identity.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Identity.php @@ -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, diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php index c072478c1..5d025bfaa 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php @@ -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'); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php index eb0fb9124..f3720b707 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/Autoconfig.php @@ -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) { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php index e34481554..f752b3f91 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php @@ -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 ); diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/idn.php b/snappymail/v/0.0.0/app/libraries/snappymail/idn.php index 25f6c029a..2e69fb1d6 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/idn.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/idn.php @@ -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); diff --git a/snappymail/v/0.0.0/app/templates/Views/User/SystemDropDown.html b/snappymail/v/0.0.0/app/templates/Views/User/SystemDropDown.html index 8639eecb7..dbc72954d 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/SystemDropDown.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/SystemDropDown.html @@ -18,7 +18,7 @@
  • -
  • diff --git a/tasks/config.js b/tasks/config.js index 638390983..01bd89b2a 100644 --- a/tasks/config.js +++ b/tasks/config.js @@ -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' ] }, diff --git a/vendors/mathiasbynens/punycode.js b/vendors/mathiasbynens/punycode.js new file mode 100644 index 000000000..b407d9a8f --- /dev/null +++ b/vendors/mathiasbynens/punycode.js @@ -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 + * @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 state to , + // 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 + ) + }; +})();