From f1640d7565015fb2dcfd321e7b023af4b95c21c2 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 9 Feb 2023 16:52:40 +0100 Subject: [PATCH 01/60] Some spacing between message "view images" buttons for #201 --- .../v/0.0.0/app/templates/Views/User/MailMessageView.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html index 181a6405e..7e75ade57 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html @@ -279,9 +279,9 @@ -
+
-
+
- +
diff --git a/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html b/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html index c6dbfa87e..539be762a 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html @@ -231,7 +231,6 @@ From 2f22267a777d2c7e2c222cb918a05587a325b335 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Fri, 10 Feb 2023 11:06:12 +0100 Subject: [PATCH 08/60] Resolve #836 --- .../v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php | 1 + .../v/0.0.0/app/libraries/MailSo/Imap/SearchCriterias.php | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) 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 b9317b9cd..f4a1ac8ec 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 @@ -617,6 +617,7 @@ class ImapClient extends \MailSo\Net\NetClient } /* // literal-string + $this->hasCapability('LITERAL+') if (\preg_match('/[\r\n\x00\x80-\xFF]/', $sStringForEscape)) { return \sprintf("{%d}\r\n%s", \strlen($sStringForEscape), $sStringForEscape); } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/SearchCriterias.php b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/SearchCriterias.php index 427345333..5a04f718e 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Imap/SearchCriterias.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Imap/SearchCriterias.php @@ -316,8 +316,11 @@ abstract class SearchCriterias public static function escapeSearchString(\MailSo\Imap\ImapClient $oImapClient, string $sSearch) : string { - return !\MailSo\Base\Utils::IsAscii($sSearch) - ? '{'.\strlen($sSearch).'}'."\r\n".$sSearch : $oImapClient->EscapeString($sSearch); +// return $oImapClient->EscapeString($sSearch); +// return \MailSo\Base\Utils::IsAscii($sSearch) || $oImapClient->hasCapability('QQMail')) + return (\MailSo\Base\Utils::IsAscii($sSearch) || !$oImapClient->hasCapability('LITERAL+')) + ? $oImapClient->EscapeString($sSearch) + : '{'.\strlen($sSearch).'}'."\r\n{$sSearch}"; } private static function parseSearchDate(string $sDate) : int From 30e3a8cc0fc311522983cc6ffcccdf11ff70501c Mon Sep 17 00:00:00 2001 From: cm-schl <63400209+cm-schl@users.noreply.github.com> Date: Fri, 10 Feb 2023 11:11:24 +0100 Subject: [PATCH 09/60] added new config options to overwrite the mail address --- .../LdapMailAccountsConfig.php | 12 +++ plugins/ldap-mail-accounts/index.php | 79 +++++++++++++------ 2 files changed, 68 insertions(+), 23 deletions(-) diff --git a/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php b/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php index f1a62786c..186fd5bee 100644 --- a/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php +++ b/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php @@ -18,6 +18,10 @@ class LdapMailAccountsConfig public const CONFIG_FIELD_USERNAME = "field_username"; public const CONFIG_SEARCH_STRING = "search_string"; public const CONFIG_FIELD_MAIL_DOMAIN = "field_domain"; + public const CONFIG_FIELD_MAIL_ADDRESS_MAIN_ACCOUNT = "field_mail_address_main_account"; + public const CONFIG_FIELD_MAIL_ADDRESS_ADDITIONAL_ACCOUNT = "field_mail_address_additional_account"; + public const CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_MAIN_ACCOUNT = "bool_overwrite_mail_address_main_account"; + public const CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_ADDITIONAL_ACCOUNT = "bool_overwrite_mail_address_additional_account"; public $server; public $protocol; @@ -30,6 +34,10 @@ class LdapMailAccountsConfig public $field_username; public $search_string; public $field_domain; + public $field_mail_address_main_account; + public $field_mail_address_additional_account; + public $bool_overwrite_mail_address_main_account; + public $bool_overwrite_mail_address_additional_account; public static function MakeConfig(Plugin $config): LdapMailAccountsConfig { @@ -45,6 +53,10 @@ class LdapMailAccountsConfig $ldap->field_username = trim($config->Get("plugin", self::CONFIG_FIELD_USERNAME)); $ldap->search_string = trim($config->Get("plugin", self::CONFIG_SEARCH_STRING)); $ldap->field_domain = trim($config->Get("plugin", self::CONFIG_FIELD_MAIL_DOMAIN)); + $ldap->bool_overwrite_mail_address_main_account = $config->Get("plugin", self::CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_MAIN_ACCOUNT); + $ldap->field_mail_address_main_account = trim($config->Get("plugin", self::CONFIG_FIELD_MAIL_ADDRESS_MAIN_ACCOUNT)); + $ldap->bool_overwrite_mail_address_additional_account = $config->Get("plugin", self::CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_ADDITIONAL_ACCOUNT); + $ldap->field_mail_address_additional_account = trim($config->Get("plugin", self::CONFIG_FIELD_MAIL_ADDRESS_ADDITIONAL_ACCOUNT)); return $ldap; } diff --git a/plugins/ldap-mail-accounts/index.php b/plugins/ldap-mail-accounts/index.php index 7b6fb48d9..84f2b2609 100644 --- a/plugins/ldap-mail-accounts/index.php +++ b/plugins/ldap-mail-accounts/index.php @@ -55,73 +55,106 @@ class LdapMailAccountsPlugin extends AbstractPlugin */ protected function configMapping(): array { + $groupOverwriteMainAccount = new \RainLoop\Plugins\PropertyCollection('Overwrite mail address of main account'); + $groupOverwriteMainAccount->exchangeArray([ + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_MAIN_ACCOUNT)->SetLabel('Enabled') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) + ->SetDefaultValue(false), + + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_MAIL_ADDRESS_MAIN_ACCOUNT) + ->SetLabel("Mail address field for main account") + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) + ->SetDescription("The ldap field containing the mail address to use on the SnappyMail main account. + \nThe value found inside ldap will overwrite the mail address of the SnappyMail main account (the account the user logged in at SnappyMail)") + ->SetDefaultValue("mail"), + ]); + + $groupOverwriteAdditionalAccount = new \RainLoop\Plugins\PropertyCollection('Overwrite mail address of additional account'); + $groupOverwriteAdditionalAccount->exchangeArray([ + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_ADDITIONAL_ACCOUNT)->SetLabel('Enabled') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) + ->SetDefaultValue(false), + + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_MAIL_ADDRESS_ADDITIONAL_ACCOUNT) + ->SetLabel("Mail address field for additional account") + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) + ->SetDescription("The ldap field containing the mail address to use on the found additional mail account. + \nThe value found inside ldap will be used as mail address of the additional mail accounts created by this plugin.") + ->SetDefaultValue("mail"), + ]); + return [ - Property::NewInstance(LdapMailAccountsConfig::CONFIG_SERVER) + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_SERVER) ->SetLabel("LDAP Server URL") ->SetPlaceholder("ldap://server:port") - ->SetType(PluginPropertyType::STRING), + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING), - Property::NewInstance(LdapMailAccountsConfig::CONFIG_PROTOCOL_VERSION) + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_PROTOCOL_VERSION) ->SetLabel("LDAP Protocol Version") - ->SetType(PluginPropertyType::SELECTION) + ->SetType(RainLoop\Enumerations\PluginPropertyType::SELECTION) ->SetDefaultValue([2, 3]), - Property::NewInstance(LdapMailAccountsConfig::CONFIG_BIND_USER) + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_BIND_USER) ->SetLabel("LDAP Username") ->SetDescription("The user to use for binding to the LDAP server. Should be a DN or RDN. Leave empty for anonymous bind.") - ->SetType(PluginPropertyType::STRING), + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING), - Property::NewInstance(LdapMailAccountsConfig::CONFIG_BIND_PASSWORD) + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_BIND_PASSWORD) ->SetLabel("LDAP Password") ->SetDescription("Leave empty for anonymous bind.") - ->SetType(PluginPropertyType::PASSWORD), + ->SetType(RainLoop\Enumerations\PluginPropertyType::PASSWORD), - Property::NewInstance(LdapMailAccountsConfig::CONFIG_OBJECTCLASS) + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_OBJECTCLASS) ->SetLabel("Object class") - ->SetType(PluginPropertyType::STRING) + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) ->SetDescription("The object class to use when searching for additional mail accounts of the logged in SnappyMail user") ->SetDefaultValue("user"), - Property::NewInstance(LdapMailAccountsConfig::CONFIG_BASE) + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_BASE) ->SetLabel("Base DN") - ->SetType(PluginPropertyType::STRING) + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) ->SetDescription("The base DN to search in for additional mail accounts of the logged in SnappyMail user"), - Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_SEARCH) + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_SEARCH) ->SetLabel("Search field") - ->SetType(PluginPropertyType::STRING) + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) ->SetDescription("The name of the ldap attribute that has to contain the here defined 'LDAP search string'.") ->SetDefaultValue("member"), - Property::NewInstance(LdapMailAccountsConfig::CONFIG_SEARCH_STRING) + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_SEARCH_STRING) ->SetLabel("LDAP search string") - ->SetType(PluginPropertyType::STRING) + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) ->SetDescription("The search string used to find ldap objects of mail accounts the user has access to. \nPossible placeholers:\n#USERNAME# - replaced with the username of the actual SnappyMail user \n#BASE_DN# - replaced with the value inside the field 'User base DN'.") ->SetDefaultValue("uid=#USERNAME#"), - Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_USERNAME) + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_USERNAME) ->SetLabel("Username field of additional account") - ->SetType(PluginPropertyType::STRING) + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) ->SetDescription("The field containing the username of the found additional mail account. \nThis username gets used by SnappyMail to login to the additional mail account. \nIf this field contains an email address, only the local-part before the @ is used.") ->SetDefaultValue("uid"), - Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_MAIL_DOMAIN) + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_MAIL_DOMAIN) ->SetLabel("Domain name field of additional account") - ->SetType(PluginPropertyType::STRING) + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) ->SetDescription("The field containing the domain name of the found additional mail account. \nThis domain gets looked up by SnappyMail to choose the right connection parameters at logging in to the additional mail account. \nIf this field contains an email address, only the domain-part after the @ is used.") ->SetDefaultValue("mail"), - Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_NAME) + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_NAME) ->SetLabel("Additional account name field") - ->SetType(PluginPropertyType::STRING) + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) ->SetDescription("The field containing the default sender name of the found additional mail account.") - ->SetDefaultValue("displayName") + ->SetDefaultValue("displayName"), + + $groupOverwriteMainAccount, + + $groupOverwriteAdditionalAccount, + ]; } } From 399ae06a5c7de778ce12c2546821bcc41428b839 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Fri, 10 Feb 2023 11:38:41 +0100 Subject: [PATCH 10/60] v2.26.0 --- .../files/usr/local/include/application.ini | 2 +- CHANGELOG.md | 29 +++++++++++++++++++ integrations/cloudron/DESCRIPTION.md | 2 +- integrations/cloudron/Dockerfile | 2 +- .../nextcloud/snappymail/appinfo/info.xml | 2 +- integrations/virtualmin/snappymail.pl | 2 +- package.json | 2 +- plugins/kolab/index.php | 6 ++-- .../MailSo/Imap/ResponseCollection.php | 4 +-- .../libraries/MailSo/Imap/SearchCriterias.php | 1 + 10 files changed, 40 insertions(+), 12 deletions(-) diff --git a/.docker/release/files/usr/local/include/application.ini b/.docker/release/files/usr/local/include/application.ini index f28bfa436..fe4e7f469 100644 --- a/.docker/release/files/usr/local/include/application.ini +++ b/.docker/release/files/usr/local/include/application.ini @@ -316,5 +316,5 @@ dev_email = "" dev_password = "" [version] -current = "2.25.5" +current = "2.26.0" saved = "Sun, 18 Dec 2022 22:10:48 +0000" diff --git a/CHANGELOG.md b/CHANGELOG.md index 96dc74288..5d9ff98c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +## 2.26.0 – 2023-02-10 + +## Added +- Whitelist advanced SPF/DKIM/DMARC valid feature + [#938](https://github.com/the-djmaze/snappymail/pull/938) + +## Changed +- \RainLoop\Providers\AddressBook\Utils functions param `Contact` changed to `VCard` + +## Fixed +- Issue with themes and Chrome cache + [#188](https://github.com/the-djmaze/snappymail/pull/188) +- Settings panel width due to display:flex + [#940](https://github.com/the-djmaze/snappymail/pull/940) +- Not respecting default theme setting + [#941](https://github.com/the-djmaze/snappymail/pull/941) +- Some files had 0755 instead of 0644 +- Some spacing between message "view images" buttons for + [#201](https://github.com/the-djmaze/snappymail/pull/201) +- Whitelist failed when empty or when using `:` + [#938](https://github.com/the-djmaze/snappymail/pull/938) +- Cosmetics of the external images whitelist menu + [#939](https://github.com/the-djmaze/snappymail/pull/939) +- PdoAddressBook ORDER BY deleted DESC to prevent sync and export issues +- Undefined variable $items in upgrade.php +- qq.com not supporting literal-string in search + [#836](https://github.com/the-djmaze/snappymail/pull/836) + + ## 2.25.5 – 2023-02-09 ## Added diff --git a/integrations/cloudron/DESCRIPTION.md b/integrations/cloudron/DESCRIPTION.md index c656b295f..eff25735e 100644 --- a/integrations/cloudron/DESCRIPTION.md +++ b/integrations/cloudron/DESCRIPTION.md @@ -1,4 +1,4 @@ -This app packages SnappyMail 2.25.5. +This app packages SnappyMail 2.26.0. SnappyMail is a simple, modern, lightweight & fast web-based email client. diff --git a/integrations/cloudron/Dockerfile b/integrations/cloudron/Dockerfile index 41bb1de48..b16033f72 100644 --- a/integrations/cloudron/Dockerfile +++ b/integrations/cloudron/Dockerfile @@ -4,7 +4,7 @@ RUN mkdir -p /app/code WORKDIR /app/code # If you change the extraction below, be sure to test on scaleway -VERSION=2.25.5 +VERSION=2.26.0 RUN wget https://github.com/the-djmaze/snappymail/releases/download/v${VERSION}/snappymail-${VERSION}.zip -O /tmp/snappymail.zip && \ unzip /tmp/snappymail.zip -d /app/code && \ rm /tmp/snappymail.zip && \ diff --git a/integrations/nextcloud/snappymail/appinfo/info.xml b/integrations/nextcloud/snappymail/appinfo/info.xml index fcaaad583..ccfe3e70f 100644 --- a/integrations/nextcloud/snappymail/appinfo/info.xml +++ b/integrations/nextcloud/snappymail/appinfo/info.xml @@ -3,7 +3,7 @@ snappymailSnappyMailSnappyMail Webmail - 2.25.5 + 2.26.0agplSnappyMail, RainLoop Team, Nextgen-Networks, Tab Fitts, Nathan Kinkade, Pierre-Alain BandinelliEscapeString($sSearch); // return \MailSo\Base\Utils::IsAscii($sSearch) || $oImapClient->hasCapability('QQMail')) return (\MailSo\Base\Utils::IsAscii($sSearch) || !$oImapClient->hasCapability('LITERAL+')) From 424c1ff90a228fdbc3cb0c69554e2c449db3f1e2 Mon Sep 17 00:00:00 2001 From: cm-schl <63400209+cm-schl@users.noreply.github.com> Date: Fri, 10 Feb 2023 12:04:50 +0100 Subject: [PATCH 11/60] Prepared ldap query to optionally read mail addresses for main account and additional accounts --- .../ldap-mail-accounts/LdapMailAccounts.php | 40 +++++++++++++++++-- .../LdapMailAccountsConfig.php | 4 +- plugins/ldap-mail-accounts/index.php | 4 +- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/plugins/ldap-mail-accounts/LdapMailAccounts.php b/plugins/ldap-mail-accounts/LdapMailAccounts.php index d82eec809..d2603f3b2 100644 --- a/plugins/ldap-mail-accounts/LdapMailAccounts.php +++ b/plugins/ldap-mail-accounts/LdapMailAccounts.php @@ -84,7 +84,11 @@ class LdapMailAccounts $this->config->objectclass, $this->config->field_name, $this->config->field_username, - $this->config->field_domain + $this->config->field_domain, + $this->config->bool_overwrite_mail_address_main_account, + $this->config->field_mail_address_main_account, + $this->config->bool_overwrite_mail_address_additional_account, + $this->config->field_mail_address_additional_account ); } catch (LdapMailAccountsException $e) { @@ -264,6 +268,10 @@ class LdapMailAccounts * @param string $nameField * @param string $usernameField * @param string $domainField + * @param bool $overwriteMailMainAccount + * @param string $mailAddressFieldMainAccount + * @param bool $overwriteMailAdditionalAccount + * @param string $mailAddressFieldAdditionalAccount * @return LdapMailAccountResult[] * @throws LdapMailAccountsException */ @@ -274,7 +282,11 @@ class LdapMailAccounts string $objectClass, string $nameField, string $usernameField, - string $domainField): array + string $domainField, + bool $overwriteMailMainAccount, + string $mailAddressFieldMainAccount, + bool $overwriteMailAdditionalAccount, + string $mailAddressFieldAdditionalAccount): array { $this->EnsureBound(); $nameField = strtolower($nameField); @@ -284,7 +296,20 @@ class LdapMailAccounts $filter = "(&(objectclass=$objectClass)($searchField=$searchString))"; $this->logger->Write("Used ldap filter to search for additional mail accounts: $filter", \LOG_NOTICE, self::LOG_KEY); - $ldapResult = @ldap_search($this->ldap, $searchBase, $filter, ['dn', $usernameField, $nameField, $domainField]); + //Set together the attributes to search inside the LDAP + $ldapAttributes = ['dn', $usernameField, $nameField, $domainField]; + if ($overwriteMailMainAccount) + { + \array_push($ldapAttributes, $mailAddressFieldMainAccount); + } + + if ($overwriteMailAdditionalAccount) + { + \array_push($ldapAttributes, $mailAddressFieldAdditionalAccount); + } + + + $ldapResult = @ldap_search($this->ldap, $searchBase, $filter, $ldapAttributes); if (!$ldapResult) { $this->HandleLdapError("Fetch $objectClass"); return []; @@ -311,6 +336,9 @@ class LdapMailAccounts $result->domain = $this->LdapGetAttribute($entry, $domainField, true, true); $result->domain = $this->RemoveEventualLocalPart($result->domain); + $result->mailMainAccount = $this->LdapGetAttribute($entry, $mailAddressFieldMainAccount, true, $overwriteMailMainAccount); + $result->mailAdditionalAccount = $this->LdapGetAttribute($entry, $mailAddressFieldAdditionalAccount, true, $overwriteMailAdditionalAccount); + $results[] = $result; } @@ -407,4 +435,10 @@ class LdapMailAccountResult /** @var string */ public $domain; + + /** @var string */ + public $mailMainAccount; + + /** @var string */ + public $mailAdditionalAccount; } diff --git a/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php b/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php index 186fd5bee..ac08699e8 100644 --- a/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php +++ b/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php @@ -18,10 +18,10 @@ class LdapMailAccountsConfig public const CONFIG_FIELD_USERNAME = "field_username"; public const CONFIG_SEARCH_STRING = "search_string"; public const CONFIG_FIELD_MAIL_DOMAIN = "field_domain"; - public const CONFIG_FIELD_MAIL_ADDRESS_MAIN_ACCOUNT = "field_mail_address_main_account"; - public const CONFIG_FIELD_MAIL_ADDRESS_ADDITIONAL_ACCOUNT = "field_mail_address_additional_account"; public const CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_MAIN_ACCOUNT = "bool_overwrite_mail_address_main_account"; + public const CONFIG_FIELD_MAIL_ADDRESS_MAIN_ACCOUNT = "field_mail_address_main_account"; public const CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_ADDITIONAL_ACCOUNT = "bool_overwrite_mail_address_additional_account"; + public const CONFIG_FIELD_MAIL_ADDRESS_ADDITIONAL_ACCOUNT = "field_mail_address_additional_account"; public $server; public $protocol; diff --git a/plugins/ldap-mail-accounts/index.php b/plugins/ldap-mail-accounts/index.php index 84f2b2609..711755499 100644 --- a/plugins/ldap-mail-accounts/index.php +++ b/plugins/ldap-mail-accounts/index.php @@ -12,11 +12,11 @@ class LdapMailAccountsPlugin extends AbstractPlugin { const NAME = 'LDAP Mail Accounts', - VERSION = '1.1', + VERSION = '2.0.0', AUTHOR = 'cm-schl', URL = 'https://github.com/cm-sch', RELEASE = '2022-12-08', - REQUIRED = '2.23.0', + REQUIRED = '2.25.4', 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).'; From 0c6950d49c60819c164ca418ee9b1d750edffa10 Mon Sep 17 00:00:00 2001 From: Nivesh Krishna Date: Fri, 10 Feb 2023 20:02:17 +0530 Subject: [PATCH 12/60] fix default theme selection --- snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Themes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Themes.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Themes.php index a9da3d72f..adda3e439 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Themes.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Themes.php @@ -92,7 +92,7 @@ trait Themes public function ValidateTheme(string $sTheme): string { - return \in_array($sTheme, $this->GetThemes()) ? $sTheme : $this->Config()->Get('themes', 'default', 'Default'); + return \in_array($sTheme, $this->GetThemes()) ? $sTheme : $this->Config()->Get('webmail', 'theme', 'Default'); } public function compileCss(string $sTheme, bool $bAdmin, bool $bMinified = false) : string From 8b02d7f892980160428d4d884578fe26fb02ad27 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Fri, 10 Feb 2023 17:31:10 +0100 Subject: [PATCH 13/60] Improved fix for #941 and #944 --- .../0.0.0/app/libraries/RainLoop/Actions/Themes.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Themes.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Themes.php index adda3e439..6b26f36c8 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Themes.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Themes.php @@ -13,9 +13,9 @@ trait Themes && ($oAccount = $this->getAccountFromToken(false)) && $this->GetCapa(\RainLoop\Enumerations\Capa::THEMES) && ($oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount))) { - $sTheme = $this->ValidateTheme((string) $oSettingsLocal->GetConf('Theme', $sTheme)) ?: $sTheme; + $sTheme = (string) $oSettingsLocal->GetConf('Theme', $sTheme); } - $sTheme = $this->ValidateTheme($sTheme) ?: 'Default'; + $sTheme = $this->ValidateTheme($sTheme); } return $sTheme; } @@ -92,7 +92,13 @@ trait Themes public function ValidateTheme(string $sTheme): string { - return \in_array($sTheme, $this->GetThemes()) ? $sTheme : $this->Config()->Get('webmail', 'theme', 'Default'); + if (!\in_array($sTheme, $this->GetThemes())) { + $sTheme = $this->Config()->Get('webmail', 'theme', 'Default'); + if (!\in_array($sTheme, $this->GetThemes())) { + $sTheme = 'Default'; + } + } + return $sTheme; } public function compileCss(string $sTheme, bool $bAdmin, bool $bMinified = false) : string From ff53f8f8fe7fb1e6e41cc75e57bcf1882d778e70 Mon Sep 17 00:00:00 2001 From: Lota Bi Date: Sat, 11 Feb 2023 18:14:22 +0100 Subject: [PATCH 14/60] Password italian traslation Password not need translation --- snappymail/v/0.0.0/app/localization/it-IT/user.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snappymail/v/0.0.0/app/localization/it-IT/user.json b/snappymail/v/0.0.0/app/localization/it-IT/user.json index 2b3a6ecc1..ec6667e7c 100644 --- a/snappymail/v/0.0.0/app/localization/it-IT/user.json +++ b/snappymail/v/0.0.0/app/localization/it-IT/user.json @@ -21,7 +21,7 @@ "NONE": "Nessuno", "NOT_SPAM": "Segnala come non spam", "USERNAME": "Nome utente", - "PASSWORD": "Parola d'ordine", + "PASSWORD": "Password", "REMEMBER": "Remember", "REPLY_TO": "Rispondi a", "SAVE": "Salva", From 4c18b8445533d0d7992adc34419dfc21aa709272 Mon Sep 17 00:00:00 2001 From: Lota Bi Date: Sat, 11 Feb 2023 18:17:40 +0100 Subject: [PATCH 15/60] Italian translation fix - password again sorry, PASSWORD lemma duplicate in admin.json too for my opinion password is a common word that not require translation in italian --- snappymail/v/0.0.0/app/localization/it-IT/admin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snappymail/v/0.0.0/app/localization/it-IT/admin.json b/snappymail/v/0.0.0/app/localization/it-IT/admin.json index 206e50e5a..6f94f3904 100644 --- a/snappymail/v/0.0.0/app/localization/it-IT/admin.json +++ b/snappymail/v/0.0.0/app/localization/it-IT/admin.json @@ -6,7 +6,7 @@ "TEST": "Test", "UPDATE": "Aggiorna", "USERNAME": "Nome utente", - "PASSWORD": "Parola d'ordine", + "PASSWORD": "Password", "CANCEL": "Annulla" }, "LOGIN": { From 820b26a3157c63127c71ed929abdb48052f284f9 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sat, 11 Feb 2023 20:00:40 +0100 Subject: [PATCH 16/60] CSS don't `display:flex` when [hidden] --- dev/Styles/Main.less | 2 +- dev/Styles/User/Layout.less | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/Styles/Main.less b/dev/Styles/Main.less index c6e0a1a1d..318ee0dd5 100644 --- a/dev/Styles/Main.less +++ b/dev/Styles/Main.less @@ -22,7 +22,7 @@ option:disabled { -webkit-tap-highlight-color: rgba(0,0,0,0); } -#rl-content { +#rl-content:not([hidden]) { display:flex; height: 100%; } diff --git a/dev/Styles/User/Layout.less b/dev/Styles/User/Layout.less index 3516bac25..f22835b85 100644 --- a/dev/Styles/User/Layout.less +++ b/dev/Styles/User/Layout.less @@ -14,7 +14,7 @@ z-index: 0; } -#rl-right { +#rl-right:not([hidden]) { display: flex; flex-grow: 1; width: 20%; From 415102aa37203777cceeba2777c2bfc08750f2f6 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sat, 11 Feb 2023 20:38:57 +0100 Subject: [PATCH 17/60] Replace Element.fromHTML() with createElement() --- dev/Common/Html.js | 3 ++- dev/Common/UtilsUser.js | 12 +++++++----- dev/Knoin/Knoin.js | 12 ++++++------ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/dev/Common/Html.js b/dev/Common/Html.js index 1b6e9d3cf..371445880 100644 --- a/dev/Common/Html.js +++ b/dev/Common/Html.js @@ -18,7 +18,8 @@ const SettingsUserStore.collapseBlockquotes() && // tpl.content.querySelectorAll('blockquote').forEach(node => { [...tpl.content.querySelectorAll('blockquote')].reverse().forEach(node => { - const el = Element.fromHTML('
•••
'); + const el = createElement('details', {class:'sm-bq-switcher'}); + el.innerHTML = '•••'; node.replaceWith(el); el.append(node); }); diff --git a/dev/Common/UtilsUser.js b/dev/Common/UtilsUser.js index e8fb0991a..6ac21ed1f 100644 --- a/dev/Common/UtilsUser.js +++ b/dev/Common/UtilsUser.js @@ -310,11 +310,13 @@ populateMessageBody = (oMessage, popup) => { oMessage.isHtml(body.classList.contains('html')); oMessage.hasImages(body.rlHasImages); } else { - body = Element.fromHTML(''); + body = createElement('div',{ + id:id, + hidden:'', + class:'b-text-part' + + (oMessage.pgpSigned() ? ' openpgp-signed' : '') + + (oMessage.pgpEncrypted() ? ' openpgp-encrypted' : '') + }); oMessage.body = body; if (!SettingsUserStore.viewHTML() || !oMessage.viewHtml()) { oMessage.viewPlain(); diff --git a/dev/Knoin/Knoin.js b/dev/Knoin/Knoin.js index 903efc231..f85245da1 100644 --- a/dev/Knoin/Knoin.js +++ b/dev/Knoin/Knoin.js @@ -1,6 +1,6 @@ import ko from 'ko'; import { koComputable } from 'External/ko'; -import { doc, $htmlCL, elementById, fireEvent } from 'Common/Globals'; +import { doc, $htmlCL, elementById, createElement, fireEvent } from 'Common/Globals'; import { forEachObjectEntry } from 'Common/Utils'; import { i18nToNodes } from 'Common/Translator'; @@ -40,9 +40,9 @@ const ViewModelClass.__vm = vm; if (vmPlace) { - vmDom = Element.fromHTML(dialog - ? '' - : ''); + vmDom = dialog + ? createElement('dialog',{id:'V-'+id}) + : createElement('div',{id:'V-'+id,hidden:''}) vmPlace.append(vmDom); vm.viewModelDom = ViewModelClass.__dom = vmDom; @@ -55,7 +55,7 @@ const vmDom.className = 'polyfill'; vmDom.showModal = () => { vmDom.backdrop || - vmDom.before(vmDom.backdrop = Element.fromHTML('
')); + vmDom.before(vmDom.backdrop = createElement('div',{class:'dialog-backdrop'})); vmDom.setAttribute('open',''); vmDom.open = true; vmDom.returnValue = null; @@ -285,7 +285,7 @@ export const }); const cross = new Crossroads(); - cross.addRoute(/^([a-zA-Z0-9-]*)\/?(.*)$/, screenOnRoute); + cross.addRoute(/^([^/]+)\/?(.*)$/, screenOnRoute); hasher.add(cross.parse.bind(cross)); hasher.init(); From e4b0d43b3dcbecaeb5f96c802cdf770a9aa21124 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sat, 11 Feb 2023 20:41:54 +0100 Subject: [PATCH 18/60] Bugfix: screenOnRoute sometimes failed --- dev/Knoin/Knoin.js | 89 +++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/dev/Knoin/Knoin.js b/dev/Knoin/Knoin.js index f85245da1..852fe2274 100644 --- a/dev/Knoin/Knoin.js +++ b/dev/Knoin/Knoin.js @@ -169,67 +169,58 @@ const * @returns {void} */ screenOnRoute = (screenName, subPart) => { - let vmScreen = null, - isSameScreen = false; - - if (null == screenName || '' == screenName) { - screenName = defaultScreenName; - } - - if (fireEvent('sm-show-screen', screenName, 1)) { - + screenName = screenName || defaultScreenName; + if (screenName && fireEvent('sm-show-screen', screenName, 1)) { // Close all popups for (let vm of visiblePopups) { (false === vm.onClose()) || vm.close(); } - if (screenName) { - vmScreen = screen(screenName); - if (!vmScreen) { - vmScreen = screen(defaultScreenName); - if (vmScreen) { - subPart = screenName + '/' + subPart; - screenName = defaultScreenName; - } + let vmScreen = screen(screenName); + if (!vmScreen) { + vmScreen = screen(defaultScreenName); + if (vmScreen) { + subPart = screenName + '/' + subPart; + screenName = defaultScreenName; + } + } + + if (vmScreen?.__started) { + let isSameScreen = currentScreen && vmScreen === currentScreen; + + if (!vmScreen.__builded) { + vmScreen.__builded = true; + + vmScreen.viewModels.forEach(ViewModelClass => + buildViewModel(ViewModelClass, vmScreen) + ); + + vmScreen.onBuild?.(); } - if (vmScreen?.__started) { - isSameScreen = currentScreen && vmScreen === currentScreen; + setTimeout(() => { + // hide screen + currentScreen && !isSameScreen && hideScreen(currentScreen); + // -- - if (!vmScreen.__builded) { - vmScreen.__builded = true; + currentScreen = vmScreen; - vmScreen.viewModels.forEach(ViewModelClass => - buildViewModel(ViewModelClass, vmScreen) - ); + // show screen + if (!isSameScreen) { + vmScreen.onShow?.(); - vmScreen.onBuild?.(); + forEachViewModel(vmScreen, (vm, dom) => { + vm.beforeShow?.(); + i18nToNodes(dom); + dom.hidden = false; + vm.onShow?.(); + autofocus(dom); + }); } + // -- - setTimeout(() => { - // hide screen - currentScreen && !isSameScreen && hideScreen(currentScreen); - // -- - - currentScreen = vmScreen; - - // show screen - if (!isSameScreen) { - vmScreen.onShow?.(); - - forEachViewModel(vmScreen, (vm, dom) => { - vm.beforeShow?.(); - i18nToNodes(dom); - dom.hidden = false; - vm.onShow?.(); - autofocus(dom); - }); - } - // -- - - vmScreen.__cross?.parse(subPart); - }, 1); - } + vmScreen.__cross?.parse(subPart); + }, 1); } } }; From d26fb2db053e43b767d4df40acff4f045a7a66b4 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sat, 11 Feb 2023 20:42:13 +0100 Subject: [PATCH 19/60] improved blockquote switcher design --- dev/Styles/User/MessageView.less | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/Styles/User/MessageView.less b/dev/Styles/User/MessageView.less index 6cc702c24..d84d14533 100644 --- a/dev/Styles/User/MessageView.less +++ b/dev/Styles/User/MessageView.less @@ -348,13 +348,13 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar { .sm-bq-switcher > summary { border: 1px solid #999; - display: block; - width: 3em; - line-height: 1em; - text-align: center; cursor: pointer; + line-height: 1em; margin: 2em 0 10px; opacity: 0.5; + padding: 0 0.5em; + text-align: center; + width: fit-content; &:hover { opacity: 1; } From 98d686bc14785e41bd43e093fc44b2eafbcf22c2 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sun, 12 Feb 2023 00:27:11 +0100 Subject: [PATCH 20/60] Cleanup AbstractViewPopup handling --- dev/Knoin/AbstractViews.js | 5 +++-- dev/Knoin/Knoin.js | 12 +----------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/dev/Knoin/AbstractViews.js b/dev/Knoin/AbstractViews.js index ac24d66ac..035f6ba17 100644 --- a/dev/Knoin/AbstractViews.js +++ b/dev/Knoin/AbstractViews.js @@ -78,9 +78,10 @@ export class AbstractViewPopup extends AbstractView afterShow() {} // Happens after showModal() animation transitionend onHide() {} // Happens before animation transitionend afterHide() {} // Happens after animation transitionend - - close() {} */ + close() { + this.modalVisible(false); + } } AbstractViewPopup.showModal = function(params = []) { diff --git a/dev/Knoin/Knoin.js b/dev/Knoin/Knoin.js index 852fe2274..d21eb3468 100644 --- a/dev/Knoin/Knoin.js +++ b/dev/Knoin/Knoin.js @@ -48,8 +48,6 @@ const vm.viewModelDom = ViewModelClass.__dom = vmDom; if (dialog) { - vm.close = () => hideScreenPopup(ViewModelClass); - // Firefox < 98 / Safari < 15.4 HTMLDialogElement not defined if (!vmDom.showModal) { vmDom.className = 'polyfill'; @@ -155,14 +153,6 @@ const }); }, - /** - * @param {Function} ViewModelClassToHide - * @returns {void} - */ - hideScreenPopup = ViewModelClassToHide => - ViewModelClassToHide?.__vm && ViewModelClassToHide.__dom - && ViewModelClassToHide.__vm.modalVisible(false), - /** * @param {string} screenName * @param {string} subPart @@ -276,7 +266,7 @@ export const }); const cross = new Crossroads(); - cross.addRoute(/^([^/]+)\/?(.*)$/, screenOnRoute); + cross.addRoute(/^([^/]*)\/?(.*)$/, screenOnRoute); hasher.add(cross.parse.bind(cross)); hasher.init(); From 6237a3691bd40c5e4ea5edde245445ecffb5a014 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sun, 12 Feb 2023 01:02:55 +0100 Subject: [PATCH 21/60] Resolve #950 --- dev/Model/Email.js | 6 +++--- snappymail/v/0.0.0/app/libraries/MailSo/Mime/Email.php | 2 +- .../v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/Model/Email.js b/dev/Model/Email.js index 30c613ee9..9217cb7a8 100644 --- a/dev/Model/Email.js +++ b/dev/Model/Email.js @@ -302,7 +302,7 @@ export class EmailModel extends AbstractModel { * @returns {string} */ hash(withoutName = false) { - return '#' + (withoutName ? '' : this.name) + '#' + this.email + '#'; + return '#' + (withoutName ? '' : this.name) + '#' + (this.email || this.name) + '#'; } /** @@ -328,8 +328,8 @@ export class EmailModel extends AbstractModel { * @returns {string} */ toLine(friendlyView, wrapWithLink) { - let result = this.email, - name = this.name, + let name = this.name, + result = this.email || name, toLink = text => '') : '') 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 599bad2ce..fbb5de7ec 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 @@ -28,7 +28,7 @@ class Email implements \JsonSerializable */ function __construct(string $sEmail, string $sDisplayName = '') { - if (!\strlen(\trim($sEmail))) { + if (!\strlen(\trim($sEmail)) && !\strlen(\trim($sDisplayName))) { throw new \InvalidArgumentException; } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php index 893167385..0e9adfdc7 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php @@ -81,7 +81,7 @@ class HeaderCollection extends \MailSo\Base\Collection if (\strlen($sValue)) { $oResult = new EmailCollection($sValue); } - return $oResult && $oResult->count() ? $oResult : null; + return $oResult; } public function ParametersByName(string $sHeaderName) : ?ParameterCollection From dbabbb93853282d55c6d1cdf6393a07148f8f34f Mon Sep 17 00:00:00 2001 From: the-djmaze <3752035+the-djmaze@users.noreply.github.com> Date: Sun, 12 Feb 2023 01:13:30 +0100 Subject: [PATCH 22/60] bugfix for solution #950 incorrect mailto link --- dev/Model/Email.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/Model/Email.js b/dev/Model/Email.js index 9217cb7a8..bb4c5a525 100644 --- a/dev/Model/Email.js +++ b/dev/Model/Email.js @@ -329,7 +329,7 @@ export class EmailModel extends AbstractModel { */ toLine(friendlyView, wrapWithLink) { let name = this.name, - result = this.email || name, + result = this.email, toLink = text => '') : '') @@ -348,7 +348,7 @@ export class EmailModel extends AbstractModel { result = toLink(); } } - return result; + return result || name; } static splitEmailLine(line) { From 477628ed70ee819ec0b347116d5003e5da3b93b7 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sun, 12 Feb 2023 01:20:20 +0100 Subject: [PATCH 23/60] #950 always return EmailCollection --- .../0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php index 0e9adfdc7..4697cfd23 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/HeaderCollection.php @@ -76,12 +76,9 @@ class HeaderCollection extends \MailSo\Base\Collection public function GetAsEmailCollection(string $sHeaderName, bool $bCharsetAutoDetect = false) : ?EmailCollection { - $oResult = null; - $sValue = $this->ValueByName($sHeaderName, $bCharsetAutoDetect); - if (\strlen($sValue)) { - $oResult = new EmailCollection($sValue); - } - return $oResult; + return new EmailCollection( + $this->ValueByName($sHeaderName, $bCharsetAutoDetect) + ); } public function ParametersByName(string $sHeaderName) : ?ParameterCollection From 396a1a423e5831b80f6f3f4237243cf0630bae51 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sun, 12 Feb 2023 18:27:06 +0100 Subject: [PATCH 24/60] Resolve #936 --- .../v/0.0.0/app/libraries/MailSo/Net/NetClient.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Net/NetClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Net/NetClient.php index e1e933899..53afe8c8e 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Net/NetClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Net/NetClient.php @@ -92,7 +92,8 @@ abstract class NetClient } if ($this->IsConnected()) { - $this->writeLogException(new Exceptions\SocketAlreadyConnectedException, \LOG_ERR); + $this->writeLogException(new Exceptions\SocketAlreadyConnectedException, \LOG_ERR, false); + return; } $this->Settings = $oSettings; @@ -214,14 +215,6 @@ abstract class NetClient return false; } - /** - * @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException - */ - public function IsConnectedWithException() : void - { - $this->IsConnected(true); - } - public function StreamContextParams() : array { return $this->rConnect && \MailSo\Base\Utils::FunctionCallable('stream_context_get_options') From 57dc1d29b3bbb8c7abcdd27681bd9230ab375e42 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sun, 12 Feb 2023 18:27:55 +0100 Subject: [PATCH 25/60] Update Identity class to latest PHP features --- .../app/libraries/RainLoop/Model/Identity.php | 78 +++++-------------- 1 file changed, 21 insertions(+), 57 deletions(-) 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 9029fd5f6..45ddf142d 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 @@ -6,50 +6,24 @@ use MailSo\Base\Utils; class Identity implements \JsonSerializable { - /** - * @var string - */ - private $sId; + private string $sId; - /** - * @var string - */ - private $sEmail; + private string $sEmail; - /** - * @var string - */ - private $sName; + private string $sName = ''; - /** - * @var string - */ - private $sReplyTo; + private string $sReplyTo = ''; - /** - * @var string - */ - private $sBcc; + private string $sBcc = ''; - /** - * @var string - */ - private $sSignature; + private string $sSignature = ''; - /** - * @var bool - */ - private $bSignatureInsertBefore; + private bool $bSignatureInsertBefore = false; function __construct(string $sId = '', string $sEmail = '') { $this->sId = $sId; $this->sEmail = $sEmail; - $this->sName = ''; - $this->sReplyTo = ''; - $this->sBcc = ''; - $this->sSignature = ''; - $this->bSignatureInsertBefore = false; } public function Id(bool $bFillOnEmpty = false): string @@ -84,16 +58,6 @@ class Identity implements \JsonSerializable return $this->sBcc; } - public function Signature(): string - { - return $this->sSignature; - } - - public function SignatureInsertBefore(): bool - { - return $this->bSignatureInsertBefore; - } - public function SetId(string $sId): Identity { $this->sId = $sId; @@ -151,13 +115,13 @@ class Identity implements \JsonSerializable public function ToSimpleJSON(): array { return array( - 'Id' => $this->Id(), - 'Email' => $this->Email(), - 'Name' => $this->Name(), - 'ReplyTo' => $this->ReplyTo(), - 'Bcc' => $this->Bcc(), - 'Signature' => $this->Signature(), - 'SignatureInsertBefore' => $this->SignatureInsertBefore() + 'Id' => $this->sId, + 'Email' => $this->sEmail, + 'Name' => $this->sName, + 'ReplyTo' => $this->sReplyTo, + 'Bcc' => $this->sBcc, + 'Signature' => $this->sSignature, + 'SignatureInsertBefore' => $this->bSignatureInsertBefore ); } @@ -165,13 +129,13 @@ class Identity implements \JsonSerializable public function jsonSerialize() { return array( - 'Id' => $this->Id(), - 'Email' => Utils::IdnToUtf8($this->Email()), - 'Name' => $this->Name(), - 'ReplyTo' => $this->ReplyTo(), - 'Bcc' => $this->Bcc(), - 'Signature' => $this->Signature(), - 'SignatureInsertBefore' => $this->SignatureInsertBefore() + 'Id' => $this->sId, + 'Email' => Utils::IdnToUtf8($this->sEmail), + 'Name' => $this->sName, + 'ReplyTo' => $this->sReplyTo, + 'Bcc' => $this->sBcc, + 'Signature' => $this->sSignature, + 'SignatureInsertBefore' => $this->bSignatureInsertBefore ); } From e8c93a1d0cb14ec5451c427e02dc663a09f03b1a Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sun, 12 Feb 2023 18:28:30 +0100 Subject: [PATCH 26/60] Improved EmailModel handling --- dev/Model/Email.js | 17 ++++------------- dev/View/User/MailBox/MessageList.js | 4 ++-- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/dev/Model/Email.js b/dev/Model/Email.js index bb4c5a525..1dc821373 100644 --- a/dev/Model/Email.js +++ b/dev/Model/Email.js @@ -266,7 +266,7 @@ export class EmailModel extends AbstractModel { this.name = name; this.dkimStatus = dkimStatus; - this.clearDuplicateName(); + this.cleanup(); } /** @@ -276,7 +276,7 @@ export class EmailModel extends AbstractModel { */ static reviveFromJson(json) { const email = super.reviveFromJson(json); - email?.clearDuplicateName(); + email?.cleanup(); return email; } @@ -286,7 +286,6 @@ export class EmailModel extends AbstractModel { clear() { this.email = ''; this.name = ''; - this.dkimStatus = 'none'; } @@ -297,18 +296,10 @@ export class EmailModel extends AbstractModel { return this.name || this.email; } - /** - * @param {boolean} withoutName = false - * @returns {string} - */ - hash(withoutName = false) { - return '#' + (withoutName ? '' : this.name) + '#' + (this.email || this.name) + '#'; - } - /** * @returns {void} */ - clearDuplicateName() { + cleanup() { if (this.name === this.email) { this.name = ''; } @@ -385,7 +376,7 @@ export class EmailModel extends AbstractModel { if (result.length) { this.name = result[0].name || ''; this.email = result[0].address || ''; - this.clearDuplicateName(); + this.cleanup(); return true; } } diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js index b2af392ea..c6cc28bfa 100644 --- a/dev/View/User/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -162,12 +162,12 @@ export class MailMessageList extends AbstractViewRight { let list = [], current, sort = FolderUserStore.sortMode() || 'DATE'; if (sort.includes('FROM')) { MessagelistUserStore.forEach(msg => { - let email = msg.from?.[0].email; + let email = msg.from[0].email; if (!current || email != current.id) { current = { id: email, label: msg.from[0].toLine(), - search: 'from=' + msg.from[0].email, + search: 'from=' + email, messages: [] }; list.push(current); From 33653eae818383e2ef4915f6780edadfb5c985ac Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Mon, 13 Feb 2023 16:15:26 +0100 Subject: [PATCH 27/60] Improved email address parsing and handling --- dev/Common/UtilsUser.js | 22 +- dev/Component/EmailAddresses.js | 29 +- dev/Mime/Utils.js | 15 +- dev/Model/Email.js | 294 ++++++------------ dev/Model/EmailCollection.js | 32 +- dev/View/Popup/Compose.js | 9 +- .../app/libraries/RainLoop/ServiceActions.php | 2 +- 7 files changed, 165 insertions(+), 238 deletions(-) diff --git a/dev/Common/UtilsUser.js b/dev/Common/UtilsUser.js index 6ac21ed1f..4d0610fd0 100644 --- a/dev/Common/UtilsUser.js +++ b/dev/Common/UtilsUser.js @@ -4,7 +4,7 @@ import { MessageSetAction, ComposeType/*, FolderType*/ } from 'Common/EnumsUser' import { doc, createElement, elementById, dropdowns, dropdownVisibility, SettingsGet, leftPanelDisabled } from 'Common/Globals'; import { plainToHtml } from 'Common/Html'; import { getNotification } from 'Common/Translator'; -import { EmailModel } from 'Model/Email'; +import { EmailCollectionModel } from 'Model/EmailCollection'; import { MessageModel } from 'Model/Message'; import { MessageUserStore } from 'Stores/User/Message'; import { MessagelistUserStore } from 'Stores/User/Messagelist'; @@ -158,27 +158,13 @@ mailToHelper = mailToUrl => { const email = mailToUrl[0], params = new URLSearchParams(mailToUrl[1]), - toEmailModel = value => null != value ? EmailModel.parseEmailLine(value) : null; + to = params.get('to'), + toEmailModel = value => EmailCollectionModel.fromString(value); showMessageComposer([ ComposeType.Empty, null, - params.get('to') - ? Object.values( - toEmailModel(email + ',' + params.get('to')).reduce((result, value) => { - if (value) { - if (result[value.email]) { - if (!result[value.email].name) { - result[value.email] = value; - } - } else { - result[value.email] = value; - } - } - return result; - }, {}) - ) - : EmailModel.parseEmailLine(email), + toEmailModel(to ? email + ',' + to : email), toEmailModel(params.get('cc')), toEmailModel(params.get('bcc')), params.get('subject'), diff --git a/dev/Component/EmailAddresses.js b/dev/Component/EmailAddresses.js index 20b4fe862..b00c0336b 100644 --- a/dev/Component/EmailAddresses.js +++ b/dev/Component/EmailAddresses.js @@ -1,8 +1,29 @@ import { doc, createElement, addEventsListeners } from 'Common/Globals'; -import { EmailModel } from 'Model/Email'; +import { EmailModel, addressparser } from 'Model/Email'; const contentType = 'snappymail/emailaddress', - getAddressKey = li => li?.emailaddress?.key; + getAddressKey = li => li?.emailaddress?.key, + + parseEmailLine = line => addressparser(line).map(item => + (item.name || item.address) + ? new EmailModel(item.address, item.name) : null + ).filter(v => v), + splitEmailLine = line => { + const result = []; + let exists = false; + addressparser(line).forEach(item => { + const address = (item.name || item.address) + ? new EmailModel(item.address, item.name) + : null; + + if (address?.email) { + exists = true; + } + + result.push(address ? address.toLine() : item.name); + }); + return exists ? result : null; + }; let dragAddress, datalist; @@ -154,8 +175,8 @@ export class EmailAddressesComponent { if (val) { const self = this, v = val.trim(), - hook = (v && [',', ';', '\n'].includes(v.slice(-1))) ? EmailModel.splitEmailLine(val) : null, - values = (hook || [val]).map(value => EmailModel.parseEmailLine(value)) + hook = (v && [',', ';', '\n'].includes(v.slice(-1))) ? splitEmailLine(val) : null, + values = (hook || [val]).map(value => parseEmailLine(value)) .flat(Infinity) .map(item => (item.toLine ? [item.toLine(), item] : [item, null])); diff --git a/dev/Mime/Utils.js b/dev/Mime/Utils.js index 64121bab0..da297fbe6 100644 --- a/dev/Mime/Utils.js +++ b/dev/Mime/Utils.js @@ -1,7 +1,6 @@ import { ParseMime } from 'Mime/Parser'; import { AttachmentModel } from 'Model/Attachment'; -import { EmailModel } from 'Model/Email'; import { FileInfo } from 'Common/File'; import { BEGIN_PGP_MESSAGE } from 'Stores/User/Pgp'; @@ -17,16 +16,10 @@ export function MimeToMessage(data, message) let html = struct.getByContentType('text/html'); html = html ? html.body : ''; - if (struct.headers.subject) { - message.subject(struct.headers.subject.value); - } - ['from','to'].forEach(name => { - if (struct.headers[name] && !message[name].length) { - let mail = new EmailModel; - mail.parse(struct.headers[name].value); - message[name].push(mail); - } - }); + message.subject(struct.headerValue('subject') || ''); + + // EmailCollectionModel + ['from','to'].forEach(name => message[name].fromString(struct.headerValue(name))); struct.forEach(part => { let cd = part.header('content-disposition'), diff --git a/dev/Model/Email.js b/dev/Model/Email.js index 1dc821373..0dcfec88a 100644 --- a/dev/Model/Email.js +++ b/dev/Model/Email.js @@ -18,32 +18,83 @@ import { AbstractModel } from 'Knoin/AbstractModel'; * @param {String} str Address field * @return {Array} An array of address objects */ -function addressparser(str) { - var tokenizer = new Tokenizer(str); - var tokens = tokenizer.tokenize(); - var addresses = []; - var address = []; - var parsedAddresses = []; +export function addressparser(str) { + str = (str || '').toString(); - tokens.forEach(token => { - if (token.type === 'operator' && (token.value === ',' || token.value === ';')) { - address.length && addresses.push(address); + let + endOperator = '', + node = { + type: 'text', + value: '' + }, + escaped = false, + address = [], + addresses = []; + + const + /* + * Operator tokens and which tokens are expected to end the sequence + */ + OPERATORS = { + '"': '"', + '(': ')', + '<': '>', + ',': '', + // Groups are ended by semicolons + ':': ';', + // Semicolons are not a legal delimiter per the RFC2822 grammar other + // than for terminating a group, but they are also not valid for any + // other use in this context. Given that some mail clients have + // historically allowed the semicolon as a delimiter equivalent to the + // comma in their UI, it makes sense to treat them the same as a comma + // when used outside of a group. + ';': '' + }, + pushToken = token => { + token.value = (token.value || '').toString().trim(); + token.value.length && address.push(token); + node = { + type: 'text', + value: '' + }, + escaped = false; + }, + pushAddress = () => { + if (address.length) { + address = _handleAddress(address); + if (address.length) { + addresses = addresses.concat(address); + } + } address = []; + }; + + [...str].forEach(chr => { + if (!escaped && (chr === endOperator || (!endOperator && chr in OPERATORS))) { + pushToken(node); + if (',' === chr || ';' === chr) { + pushAddress(); + } else { + endOperator = endOperator ? '' : OPERATORS[chr]; + if ('<' === chr) { + node.type = 'address'; + } else if ('(' === chr) { + node.type = 'comment'; + } else if (':' === chr) { + node.type = 'group'; + } + } } else { - address.push(token); + node.value += chr; + escaped = !escaped && '\\' === chr; } }); + pushToken(node); - address.length && addresses.push(address); + pushAddress(); - addresses.forEach(address => { - address = _handleAddress(address); - if (address.length) { - parsedAddresses = parsedAddresses.concat(address); - } - }); - - return parsedAddresses; + return addresses; +// return addresses.map(item => (item.name || item.address) ? new EmailModel(item.address, item.name) : null).filter(v => v); } /** @@ -53,37 +104,21 @@ function addressparser(str) { * @return {Object} Address object */ function _handleAddress(tokens) { - var isGroup = false; - var state = 'text'; - var address = void 0; - var addresses = []; - var data = { - address: [], - comment: [], - group: [], - text: [] - }; + let + isGroup = false, + address = {}, + addresses = [], + data = { + address: [], + comment: [], + group: [], + text: [] + }; // Filter out , (comments) and regular text tokens.forEach(token => { - if (token.type === 'operator') { - switch (token.value) { - case '<': - state = 'address'; - break; - case '(': - state = 'comment'; - break; - case ':': - state = 'group'; - isGroup = true; - break; - default: - state = 'text'; - } - } else if (token.value) { - data[state].push(token.value); - } + isGroup = isGroup || 'group' === token.type; + data[token.type].push(token.value); }); // If there is no text but a comment, replace the two @@ -94,10 +129,11 @@ function _handleAddress(tokens) { if (isGroup) { // http://tools.ietf.org/html/rfc2822#appendix-A.1.3 - data.text = data.text.join(' '); addresses.push({ - name: data.text || address && address.name, - group: data.group.length ? addressparser(data.group.join(',')) : [] + address: '', + name: data.text.join(' ').trim(), + group: addressparser(data.group.join(',')) +// ,comment: data.comment.join(' ').trim() }); } else { // If no address was found, try to detect one from regular text @@ -128,7 +164,7 @@ function _handleAddress(tokens) { } } - // If there's still is no text but a comment exixts, replace the two + // If there's still is no text but a comment exists, replace the two if (!data.text.length && data.comment.length) { data.text = data.comment; data.comment = []; @@ -139,133 +175,40 @@ function _handleAddress(tokens) { data.text = data.text.concat(data.address.splice(1)); } - // Join values with spaces - data.text = data.text.join(' '); - data.address = data.address.join(' '); - - if (!data.address && isGroup) { - return []; - } address = { - address: data.address || data.text || '', - name: data.text || data.address || '' + // Join values with spaces + address: data.address.join(' ').trim(), + name: data.text.join(' ').trim() +// ,comment: data.comment.join(' ').trim() }; if (address.address === address.name) { - if ((address.address || '').match(/@/)) { + if (address.address.includes('@')) { address.name = ''; } else { address.address = ''; } } +// address.address = address.address.replace(/^[<]+(.*)[>]+$/g, '$1'); + addresses.push(address); } return addresses; } -/* - * Operator tokens and which tokens are expected to end the sequence - */ -var OPERATORS = { - '"': '"', - '(': ')', - '<': '>', - ',': '', - // Groups are ended by semicolons - ':': ';', - // Semicolons are not a legal delimiter per the RFC2822 grammar other - // than for terminating a group, but they are also not valid for any - // other use in this context. Given that some mail clients have - // historically allowed the semicolon as a delimiter equivalent to the - // comma in their UI, it makes sense to treat them the same as a comma - // when used outside of a group. - ';': '' -}; - -class Tokenizer -{ - constructor(str) { - this.str = (str || '').toString(); - this.operatorCurrent = ''; - this.operatorExpecting = ''; - this.node = null; - this.escaped = false; - this.list = []; - } - - tokenize() { - var list = []; - [...this.str].forEach(c => this.checkChar(c)); - - this.list.forEach(node => { - node.value = (node.value || '').toString().trim(); - node.value && list.push(node); - }); - - return list; - } - - checkChar(chr) { - if ((chr in OPERATORS || chr === '\\') && this.escaped) { - this.escaped = false; - } else if (this.operatorExpecting && chr === this.operatorExpecting) { - this.node = { - type: 'operator', - value: chr - }; - this.list.push(this.node); - this.node = null; - this.operatorExpecting = ''; - this.escaped = false; - return; - } else if (!this.operatorExpecting && chr in OPERATORS) { - this.node = { - type: 'operator', - value: chr - }; - this.list.push(this.node); - this.node = null; - this.operatorExpecting = OPERATORS[chr]; - this.escaped = false; - return; - } - - if (!this.escaped && chr === '\\') { - this.escaped = true; - return; - } - - if (!this.node) { - this.node = { - type: 'text', - value: '' - }; - this.list.push(this.node); - } - - if (this.escaped && chr !== '\\') { - this.node.value += '\\'; - } - - this.node.value += chr; - this.escaped = false; - } -} - export class EmailModel extends AbstractModel { /** * @param {string=} email = '' * @param {string=} name = '' * @param {string=} dkimStatus = 'none' */ - constructor(email = '', name = '', dkimStatus = 'none') { + constructor(email, name, dkimStatus = 'none') { super(); - this.email = email; - this.name = name; + this.email = email || ''; + this.name = name || ''; this.dkimStatus = dkimStatus; - this.cleanup(); } @@ -277,7 +220,7 @@ export class EmailModel extends AbstractModel { static reviveFromJson(json) { const email = super.reviveFromJson(json); email?.cleanup(); - return email; + return email?.validate() ? email : null; } /** @@ -341,45 +284,4 @@ export class EmailModel extends AbstractModel { } return result || name; } - - static splitEmailLine(line) { - const result = []; - let exists = false; - addressparser(line).forEach(item => { - const address = item.address - ? new EmailModel(item.address.replace(/^[<]+(.*)[>]+$/g, '$1'), item.name || '') - : null; - - if (address?.email) { - exists = true; - } - - result.push(address ? address.toLine() : item.name); - }); - return exists ? result : null; - } - - static parseEmailLine(line) { - return addressparser(line).map(item => - item.address ? new EmailModel(item.address.replace(/^[<]+(.*)[>]+$/g, '$1'), item.name || '') : null - ).filter(v => v); - } - - /** - * @param {string} emailAddress - * @returns {boolean} - */ - parse(emailAddress) { - emailAddress = emailAddress.trim(); - if (emailAddress) { - const result = addressparser(emailAddress); - if (result.length) { - this.name = result[0].name || ''; - this.email = result[0].address || ''; - this.cleanup(); - return true; - } - } - return false; - } } diff --git a/dev/Model/EmailCollection.js b/dev/Model/EmailCollection.js index 624ace5d2..a9dc69633 100644 --- a/dev/Model/EmailCollection.js +++ b/dev/Model/EmailCollection.js @@ -1,5 +1,6 @@ import { AbstractCollectionModel } from 'Model/AbstractCollection'; -import { EmailModel } from 'Model/Email'; +import { EmailModel, addressparser } from 'Model/Email'; +import { forEachObjectValue } from 'Common/Utils'; 'use strict'; @@ -13,6 +14,16 @@ export class EmailCollectionModel extends AbstractCollectionModel return super.reviveFromJson(items, email => EmailModel.reviveFromJson(email)); } + /** + * @param {string} text + * @returns {EmailCollectionModel} + */ + static fromString(str) { + let list = new this(); + list.fromString(str); + return list; + } + /** * @param {boolean=} friendlyView = false * @param {boolean=} wrapWithLink = false @@ -21,4 +32,23 @@ export class EmailCollectionModel extends AbstractCollectionModel toString(friendlyView, wrapWithLink) { return this.map(email => email.toLine(friendlyView, wrapWithLink)).join(', '); } + + /** + * @param {string} text + */ + fromString(str) { + if (str) { + let items = {}, key; + addressparser(str).forEach(item => { + item = new EmailModel(item.address, item.name); + // Make them unique + key = item.email || item.name; + if (key && (item.name || !items[key])) { + items[key] = item; + } + }); + forEachObjectValue(items, item => this.push(item)); + } + } + } diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 9ba7e67cd..bb4347e08 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -37,7 +37,7 @@ import { MessagelistUserStore } from 'Stores/User/Messagelist'; import Remote from 'Remote/User/Fetch'; import { ComposeAttachmentModel } from 'Model/ComposeAttachment'; -import { EmailModel } from 'Model/Email'; +import { EmailModel, addressparser } from 'Model/Email'; import { decorateKoCommands, showScreenPopup } from 'Knoin/Knoin'; import { AbstractViewPopup } from 'Knoin/AbstractViews'; @@ -59,12 +59,7 @@ const base64_encode = text => btoa(unescape(encodeURIComponent(text))).match(/.{1,76}/g).join('\r\n'), - email = new EmailModel(), - getEmail = value => { - email.clear(); - email.parse(value.trim()); - return email.email || false; - }, + getEmail = value => addressparser(value)[0]?.address || false, /** * @param {Array} aList diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/ServiceActions.php b/snappymail/v/0.0.0/app/libraries/RainLoop/ServiceActions.php index 20758d2d2..b8d59ad16 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/ServiceActions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/ServiceActions.php @@ -551,7 +551,7 @@ class ServiceActions { $this->oHttp->ServerNoCache(); $sTo = \trim($_GET['to'] ?? ''); - if (!empty($sTo) && \preg_match('/^mailto:/i', $sTo)) { + if (\preg_match('/^mailto:/i', $sTo)) { \SnappyMail\Cookies::set( Actions::AUTH_MAILTO_TOKEN_KEY, Utils::EncodeKeyValuesQ(array( From ceac15ace98c5beb70377d25fd967775276fd96a Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Mon, 13 Feb 2023 16:36:38 +0100 Subject: [PATCH 28/60] Bugfix: middleclick failed when messagesDom not yet initialized --- dev/Common/UtilsUser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/Common/UtilsUser.js b/dev/Common/UtilsUser.js index 4d0610fd0..8eb0c4317 100644 --- a/dev/Common/UtilsUser.js +++ b/dev/Common/UtilsUser.js @@ -314,8 +314,8 @@ populateMessageBody = (oMessage, popup) => { messagesDom.append(body); popup || (oMessage.body.hidden = false); - popup && oMessage.viewPopupMessage(); } + popup && oMessage.viewPopupMessage(); MessageFlagsCache.initMessage(oMessage); if (oMessage.isUnseen()) { From 974acaab90051986ae88e677b9038119e7cd0fa2 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Mon, 13 Feb 2023 17:05:44 +0100 Subject: [PATCH 29/60] Cleanup EmailModel --- dev/Model/Email.js | 21 ++------------------- dev/View/Popup/Contacts.js | 2 +- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/dev/Model/Email.js b/dev/Model/Email.js index 0dcfec88a..8b189407b 100644 --- a/dev/Model/Email.js +++ b/dev/Model/Email.js @@ -220,22 +220,13 @@ export class EmailModel extends AbstractModel { static reviveFromJson(json) { const email = super.reviveFromJson(json); email?.cleanup(); - return email?.validate() ? email : null; - } - - /** - * @returns {void} - */ - clear() { - this.email = ''; - this.name = ''; - this.dkimStatus = 'none'; + return email?.valid() ? email : null; } /** * @returns {boolean} */ - validate() { + valid() { return this.name || this.email; } @@ -248,14 +239,6 @@ export class EmailModel extends AbstractModel { } } - /** - * @param {string} query - * @returns {boolean} - */ - search(query) { - return (this.name + ' ' + this.email).toLowerCase().includes(query.toLowerCase()); - } - /** * @param {boolean} friendlyView = false * @param {boolean} wrapWithLink = false diff --git a/dev/View/Popup/Contacts.js b/dev/View/Popup/Contacts.js index 27d55fb22..a3f667e45 100644 --- a/dev/View/Popup/Contacts.js +++ b/dev/View/Popup/Contacts.js @@ -140,7 +140,7 @@ export class ContactsPopupView extends AbstractViewPopup { this.contactsCheckedOrSelected().forEach(oContact => { const data = oContact?.getNameAndEmailHelper(), email = data ? new EmailModel(data[0], data[1]) : null; - email?.validate() && aE.push(email); + email?.valid() && aE.push(email); }); if (arrayLength(aE)) { From d898178985955f5491e6a05d42e20d95e273ed0b Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Mon, 13 Feb 2023 19:19:00 +0100 Subject: [PATCH 30/60] emailArrayToStringLineHelper() filter addresses without email address and simplify ComposeType handling --- dev/View/Popup/Compose.js | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index bb4347e08..2fe4eebf7 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -67,7 +67,7 @@ const * @returns {string} */ emailArrayToStringLineHelper = (aList, bFriendly) => - aList.map(item => item.toLine(bFriendly)).join(', '), + aList.filter(item => item.email).map(item => item.toLine(bFriendly)).join(', '), reloadDraftFolder = () => { const draftsFolder = FolderUserStore.draftsFolder(); @@ -200,10 +200,6 @@ export class ComposePopupView extends AbstractViewPopup { }; this.oEditor = null; - this.aDraftInfo = null; - this.sInReplyTo = ''; - this.bFromDraft = false; - this.sReferences = ''; this.sLastFocusedField = 'to'; @@ -849,33 +845,17 @@ export class ComposePopupView extends AbstractViewPopup { break; case ComposeType.Draft: - this.to(emailArrayToStringLineHelper(message.to)); - this.cc(emailArrayToStringLineHelper(message.cc)); - this.bcc(emailArrayToStringLineHelper(message.bcc)); - this.replyTo(emailArrayToStringLineHelper(message.replyTo)); - this.bFromDraft = true; - this.draftsFolder(message.folder); this.draftUid(message.uid); - - this.subject(sSubject); - this.prepareMessageAttachments(message, msgComposeType); - - this.aDraftInfo = 3 === arrayLength(aDraftInfo) ? aDraftInfo : null; - this.sInReplyTo = message.inReplyTo; - this.sReferences = message.references; - break; - + // fallthrough case ComposeType.EditAsNew: this.to(emailArrayToStringLineHelper(message.to)); this.cc(emailArrayToStringLineHelper(message.cc)); this.bcc(emailArrayToStringLineHelper(message.bcc)); this.replyTo(emailArrayToStringLineHelper(message.replyTo)); - this.subject(sSubject); this.prepareMessageAttachments(message, msgComposeType); - this.aDraftInfo = 3 === arrayLength(aDraftInfo) ? aDraftInfo : null; this.sInReplyTo = message.inReplyTo; this.sReferences = message.references; From ebf429215b7c371e60bd9e26ce4be04a23c4b15c Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Mon, 13 Feb 2023 19:46:30 +0100 Subject: [PATCH 31/60] view Message in new tab/window was missing BCC --- dev/Model/Message.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dev/Model/Message.js b/dev/Model/Message.js index a55b9cdb5..97d5b044f 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -335,18 +335,21 @@ export class MessageModel extends AbstractModel { viewPopupMessage(print) { const timeStampInUTC = this.dateTimeStampInUTC() || 0, ccLine = this.cc.toString(), + bccLine = this.bcc.toString(), m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null, win = open(''), - sdoc = win.document; - let subject = encodeHtml(this.subject()), + sdoc = win.document, + subject = encodeHtml(this.subject()), mode = this.isHtml() ? 'div' : 'pre', - cc = ccLine ? `
${encodeHtml(i18n('GLOBAL/CC'))}: ${encodeHtml(ccLine)}
` : '', + to = `
${encodeHtml(i18n('GLOBAL/TO'))}: ${encodeHtml(this.to)}
` + + (ccLine ? `
${encodeHtml(i18n('GLOBAL/CC'))}: ${encodeHtml(ccLine)}
` : '') + + (bccLine ? `
${encodeHtml(i18n('GLOBAL/BCC'))}: ${encodeHtml(bccLine)}
` : ''), style = getComputedStyle(doc.querySelector('.messageView')), prop = property => style.getPropertyValue(property); sdoc.write(PreviewHTML .replace('', '<title>'+subject) // eslint-disable-next-line max-len - .replace('<body>', `<body style="background-color:${prop('background-color')};color:${prop('color')}"><header><h1>${subject}</h1><time>${encodeHtml(m ? m.format('LLL',0,LanguageStore.hourCycle()) : '')}</time><div>${encodeHtml(this.from)}</div><div>${encodeHtml(i18n('GLOBAL/TO'))}: ${encodeHtml(this.to)}</div>${cc}</header><${mode}>${this.bodyAsHTML()}</${mode}>`) + .replace('<body>', `<body style="background-color:${prop('background-color')};color:${prop('color')}"><header><h1>${subject}</h1><time>${encodeHtml(m ? m.format('LLL',0,LanguageStore.hourCycle()) : '')}</time><div>${encodeHtml(this.from)}</div>${to}</header><${mode}>${this.bodyAsHTML()}</${mode}>`) ); sdoc.close(); From a696d6c0a14147c32c149f037db125e819c9178a Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Mon, 13 Feb 2023 19:47:24 +0100 Subject: [PATCH 32/60] Bugfix: addressparser() didn't handle groups properly --- dev/Component/EmailAddresses.js | 8 +++---- dev/Model/Email.js | 40 +++++++++++++++++---------------- dev/Model/EmailCollection.js | 2 +- dev/View/Popup/Compose.js | 2 +- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/dev/Component/EmailAddresses.js b/dev/Component/EmailAddresses.js index b00c0336b..70db0536d 100644 --- a/dev/Component/EmailAddresses.js +++ b/dev/Component/EmailAddresses.js @@ -5,15 +5,15 @@ const contentType = 'snappymail/emailaddress', getAddressKey = li => li?.emailaddress?.key, parseEmailLine = line => addressparser(line).map(item => - (item.name || item.address) - ? new EmailModel(item.address, item.name) : null + (item.name || item.email) + ? new EmailModel(item.email, item.name) : null ).filter(v => v), splitEmailLine = line => { const result = []; let exists = false; addressparser(line).forEach(item => { - const address = (item.name || item.address) - ? new EmailModel(item.address, item.name) + const address = (item.name || item.email) + ? new EmailModel(item.email, item.name) : null; if (address?.email) { diff --git a/dev/Model/Email.js b/dev/Model/Email.js index 8b189407b..4a4a15c97 100644 --- a/dev/Model/Email.js +++ b/dev/Model/Email.js @@ -77,7 +77,7 @@ export function addressparser(str) { } else { endOperator = endOperator ? '' : OPERATORS[chr]; if ('<' === chr) { - node.type = 'address'; + node.type = 'email'; } else if ('(' === chr) { node.type = 'comment'; } else if (':' === chr) { @@ -94,7 +94,7 @@ export function addressparser(str) { pushAddress(); return addresses; -// return addresses.map(item => (item.name || item.address) ? new EmailModel(item.address, item.name) : null).filter(v => v); +// return addresses.map(item => (item.name || item.email) ? new EmailModel(item.email, item.name) : null).filter(v => v); } /** @@ -109,13 +109,12 @@ function _handleAddress(tokens) { address = {}, addresses = [], data = { - address: [], + email: [], comment: [], group: [], text: [] }; - // Filter out <addresses>, (comments) and regular text tokens.forEach(token => { isGroup = isGroup || 'group' === token.type; data[token.type].push(token.value); @@ -129,68 +128,71 @@ function _handleAddress(tokens) { if (isGroup) { // http://tools.ietf.org/html/rfc2822#appendix-A.1.3 +/* addresses.push({ - address: '', + email: '', name: data.text.join(' ').trim(), group: addressparser(data.group.join(',')) // ,comment: data.comment.join(' ').trim() }); +*/ + addresses = addresses.concat(addressparser(data.group.join(','))); } else { // If no address was found, try to detect one from regular text - if (!data.address.length && data.text.length) { + if (!data.email.length && data.text.length) { var i = data.text.length; while (i--) { if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) { - data.address = data.text.splice(i, 1); + data.email = data.text.splice(i, 1); break; } } // still no address - if (!data.address.length) { + if (!data.email.length) { i = data.text.length; while (i--) { data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^@\s]+\b\s*/, address => { - if (!data.address.length) { - data.address = [address.trim()]; + if (!data.email.length) { + data.email = [address.trim()]; return ''; } return address.trim(); }); - if (data.address.length) { + if (data.email.length) { break; } } } } - // If there's still is no text but a comment exists, replace the two + // If there's still no text but a comment exists, replace the two if (!data.text.length && data.comment.length) { data.text = data.comment; data.comment = []; } // Keep only the first address occurence, push others to regular text - if (data.address.length > 1) { - data.text = data.text.concat(data.address.splice(1)); + if (data.email.length > 1) { + data.text = data.text.concat(data.email.splice(1)); } address = { // Join values with spaces - address: data.address.join(' ').trim(), + email: data.email.join(' ').trim(), name: data.text.join(' ').trim() // ,comment: data.comment.join(' ').trim() }; - if (address.address === address.name) { - if (address.address.includes('@')) { + if (address.email === address.name) { + if (address.email.includes('@')) { address.name = ''; } else { - address.address = ''; + address.email = ''; } } -// address.address = address.address.replace(/^[<]+(.*)[>]+$/g, '$1'); +// address.email = address.email.replace(/^[<]+(.*)[>]+$/g, '$1'); addresses.push(address); } diff --git a/dev/Model/EmailCollection.js b/dev/Model/EmailCollection.js index a9dc69633..ab2d24aa8 100644 --- a/dev/Model/EmailCollection.js +++ b/dev/Model/EmailCollection.js @@ -40,7 +40,7 @@ export class EmailCollectionModel extends AbstractCollectionModel if (str) { let items = {}, key; addressparser(str).forEach(item => { - item = new EmailModel(item.address, item.name); + item = new EmailModel(item.email, item.name); // Make them unique key = item.email || item.name; if (key && (item.name || !items[key])) { diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 2fe4eebf7..8eda55fc5 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -59,7 +59,7 @@ const base64_encode = text => btoa(unescape(encodeURIComponent(text))).match(/.{1,76}/g).join('\r\n'), - getEmail = value => addressparser(value)[0]?.address || false, + getEmail = value => addressparser(value)[0]?.email || false, /** * @param {Array} aList From b7a68a424594c9d8157dfff0f1f6258c43b8cdd3 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 09:17:53 +0100 Subject: [PATCH 33/60] Fix message @media print --- dev/Styles/@Main.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dev/Styles/@Main.less b/dev/Styles/@Main.less index 26eed8cea..28c969910 100644 --- a/dev/Styles/@Main.less +++ b/dev/Styles/@Main.less @@ -62,9 +62,14 @@ #rl-left { display: none; } + #rl-content, #rl-right { + display: block; + width: 100%; + } .message-selected { .close, .infoParent, + .top-toolbar, .messageView .controls-handle, #tags-dropdown-id, .message-fixed-button-toolbar, @@ -75,6 +80,7 @@ } #V-MailMessageView { left: 0; + overflow: initial; position: initial; } .messageView, From 614e6c5756560eb218733e0ce3f81e1ba1a834eb Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 10:11:25 +0100 Subject: [PATCH 34/60] Don't clone message for viewing, so that there is instant interaction with messagelist item --- dev/Common/Folders.js | 4 +- dev/Common/UtilsUser.js | 83 ++++++++-------------------- dev/Model/Message.js | 34 +++++------- dev/Model/MessageCollection.js | 6 +- dev/View/User/MailBox/MessageList.js | 3 +- 5 files changed, 47 insertions(+), 83 deletions(-) diff --git a/dev/Common/Folders.js b/dev/Common/Folders.js index 36349b741..2802940f0 100644 --- a/dev/Common/Folders.js +++ b/dev/Common/Folders.js @@ -129,7 +129,7 @@ folderInformation = (folder, list) => { const oldHash = folderFromCache.hash, unreadCountChange = (folderFromCache.unreadEmails() !== result.unreadEmails); -// folderFromCache.revivePropertiesFromJson(result); +// folderFromCache.revivePropertiesFromJson(result); folderFromCache.expires = Date.now(); folderFromCache.uidNext = result.uidNext; folderFromCache.hash = result.hash; @@ -151,9 +151,11 @@ folderInformation = (folder, list) => { if (!oldHash || unreadCountChange || result.hash !== oldHash) { if (folderFromCache.fullName === FolderUserStore.currentFolderFullName()) { MessagelistUserStore.reload(); +/* } else if (getFolderInboxName() === folderFromCache.fullName) { // Remote.messageList(null, {folder: getFolderFromCacheList(getFolderInboxName())}, true); Remote.messageList(null, {folder: getFolderInboxName()}, true); +*/ } } } diff --git a/dev/Common/UtilsUser.js b/dev/Common/UtilsUser.js index 8eb0c4317..6f5176d97 100644 --- a/dev/Common/UtilsUser.js +++ b/dev/Common/UtilsUser.js @@ -258,36 +258,23 @@ populateMessageBody = (oMessage, popup) => { } } else { let json = oData?.Result; - - if ( - json && - MessageModel.validJson(json) && - oMessage.folder === json.folder + if (json + && MessageModel.validJson(json) + && oMessage.hash === json.hash +// && oMessage.folder === json.folder +// && oMessage.uid == json.uid + && oMessage.revivePropertiesFromJson(json) ) { - const threads = oMessage.threads(), - isNew = !popup && oMessage.uid != json.uid && threads.includes(json.uid), - messagesDom = MessageUserStore.bodiesDom(); - if (isNew) { - oMessage = MessageModel.reviveFromJson(json); - if (oMessage) { - oMessage.threads(threads); - MessageFlagsCache.initMessage(oMessage); - - // Set clone - oMessage = MessageModel.fromMessageListItem(oMessage); - } - MessageUserStore.message(oMessage); - } - - if (oMessage && oMessage.uid == json.uid) { - popup || MessageUserStore.error(''); /* - if (bCached) { - delete json.flags; - } + if (bCached) { + delete json.flags; + } */ - isNew || oMessage.revivePropertiesFromJson(json); - + if (popup) { + oMessage.viewPopupMessage(); + } else { + MessageUserStore.error(''); + const messagesDom = MessageUserStore.bodiesDom(); if (messagesDom) { let id = 'rl-msg-' + oMessage.hash.replace(/[^a-zA-Z0-9]/g, ''), body = elementById(id); @@ -313,42 +300,16 @@ populateMessageBody = (oMessage, popup) => { messagesDom.append(body); - popup || (oMessage.body.hidden = false); + oMessage.body.hidden = false; } - popup && oMessage.viewPopupMessage(); + } - MessageFlagsCache.initMessage(oMessage); - if (oMessage.isUnseen()) { - MessageUserStore.MessageSeenTimer = setTimeout( - () => MessagelistUserStore.setAction(oMessage.folder, MessageSetAction.SetSeen, [oMessage]), - SettingsUserStore.messageReadDelay() * 1000 // seconds - ); - } - - if (isNew) { - let selectedMessage = MessagelistUserStore.selectedMessage(); - if ( - selectedMessage && - (oMessage.folder !== selectedMessage.folder || oMessage.uid != selectedMessage.uid) - ) { - MessagelistUserStore.selectedMessage(null); - if (1 === MessagelistUserStore.length) { - MessagelistUserStore.focusedMessage(null); - } - } else if (!selectedMessage) { - selectedMessage = MessagelistUserStore.find( - subMessage => - subMessage && - subMessage.folder === oMessage.folder && - subMessage.uid == oMessage.uid - ); - - if (selectedMessage) { - MessagelistUserStore.selectedMessage(selectedMessage); - MessagelistUserStore.focusedMessage(selectedMessage); - } - } - } + MessageFlagsCache.initMessage(oMessage); + if (oMessage.isUnseen()) { + MessageUserStore.MessageSeenTimer = setTimeout( + () => MessagelistUserStore.setAction(oMessage.folder, MessageSetAction.SetSeen, [oMessage]), + SettingsUserStore.messageReadDelay() * 1000 // seconds + ); } } } diff --git a/dev/Model/Message.js b/dev/Model/Message.js index 97d5b044f..d05f6ef7a 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -5,7 +5,7 @@ import { i18n } from 'Common/Translator'; import { doc, SettingsGet } from 'Common/Globals'; import { encodeHtml, plainToHtml, htmlToPlain, cleanHtml } from 'Common/Html'; -import { isFunction, forEachObjectEntry } from 'Common/Utils'; +import { forEachObjectEntry } from 'Common/Utils'; import { serverRequestRaw, proxy } from 'Common/Links'; import { addObservablesTo, addComputablesTo } from 'External/ko'; @@ -212,6 +212,7 @@ export class MessageModel extends AbstractModel { // this.attachments(AttachmentCollectionModel.reviveFromJson(json.attachments, this.foundCIDs)); this.computeSenderEmail(); + return true; } } @@ -353,9 +354,7 @@ export class MessageModel extends AbstractModel { ); sdoc.close(); - if (print) { - setTimeout(() => win.print(), 100); - } + print && setTimeout(() => win.print(), 100); } /** @@ -377,24 +376,21 @@ export class MessageModel extends AbstractModel { } /** - * @param {MessageModel} message * @returns {MessageModel} - */ - static fromMessageListItem(message) { + *//* + clone() { let self = new MessageModel(); - if (message) { - // Clone message values - forEachObjectEntry(message, (key, value) => { - if (ko.isObservable(value)) { - ko.isComputed(value) || self[key](value()); - } else if (!isFunction(value)) { - self[key] = value; - } - }); - self.computeSenderEmail(); - } + // Clone message values + forEachObjectEntry(this, (key, value) => { + if (ko.isObservable(value)) { + ko.isComputed(value) || self[key](value()); + } else if (!isFunction(value)) { + self[key] = value; + } + }); + self.computeSenderEmail(); return self; - } + }*/ showExternalImages(regex) { const body = this.body; diff --git a/dev/Model/MessageCollection.js b/dev/Model/MessageCollection.js index 7db5df4b9..a3be0fb6b 100644 --- a/dev/Model/MessageCollection.js +++ b/dev/Model/MessageCollection.js @@ -1,5 +1,6 @@ import { AbstractCollectionModel } from 'Model/AbstractCollection'; import { MessageModel } from 'Model/Message'; +import { MessageUserStore } from 'Stores/User/Message'; import { MessageFlagsCache @@ -31,8 +32,11 @@ export class MessageCollectionModel extends AbstractCollectionModel * @returns {MessageCollectionModel} */ static reviveFromJson(object, cached) { + let msg = MessageUserStore.message(); return super.reviveFromJson(object, message => { - message = MessageModel.reviveFromJson(message); + // If message is currently viewed, use that. + // Maybe then use msg.revivePropertiesFromJson(message) ? + message = (msg && msg.hash === message.hash) ? msg : MessageModel.reviveFromJson(message); if (message) { message.deleted(false); cached ? MessageFlagsCache.initMessage(message) : MessageFlagsCache.store(message); diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js index c6cc28bfa..5542b66da 100644 --- a/dev/View/User/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -235,7 +235,8 @@ export class MailMessageList extends AbstractViewRight { this.selector.on('ItemSelect', message => { if (message) { - populateMessageBody(MessageModel.fromMessageListItem(message)); +// populateMessageBody(message.clone()); + populateMessageBody(message); } else { MessageUserStore.message(null); } From e2391d1ad227737fe64a87878c403d8357881686 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 10:21:26 +0100 Subject: [PATCH 35/60] Idea code for #951 --- dev/Model/Message.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dev/Model/Message.js b/dev/Model/Message.js index d05f6ef7a..e19415c04 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -334,11 +334,14 @@ export class MessageModel extends AbstractModel { } viewPopupMessage(print) { - const timeStampInUTC = this.dateTimeStampInUTC() || 0, + const + timeStampInUTC = this.dateTimeStampInUTC() || 0, ccLine = this.cc.toString(), bccLine = this.bcc.toString(), m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null, - win = open(''), + win = open('', 'sm-msg-'+this.requestHash + /*,newWindow ? 'innerWidth=' + elementById('V-MailMessageView').clientWidth : ''*/ + ), sdoc = win.document, subject = encodeHtml(this.subject()), mode = this.isHtml() ? 'div' : 'pre', From 1f86b734147726bf56d1dc27a400fc20d64cee62 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 11:37:08 +0100 Subject: [PATCH 36/60] Bugfix: OpenPGP decrypted attachments friendlySize not a function #89 --- dev/Mime/Utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/Mime/Utils.js b/dev/Mime/Utils.js index da297fbe6..8cf6c795e 100644 --- a/dev/Mime/Utils.js +++ b/dev/Mime/Utils.js @@ -33,7 +33,7 @@ export function MimeToMessage(data, message) attachment.fileNameExt = attachment.fileName.replace(/^.+(\.[a-z]+)$/, '$1'); attachment.fileType = FileInfo.getType('', type.value); attachment.url = part.dataUrl; - attachment.friendlySize = FileInfo.friendlySize(part.body.length); + attachment.estimatedSize = part.body.length; /* attachment.isThumbnail = false; attachment.contentLocation = ''; From f99daeebe55bd76d5495df50b7e62ee74b8f108f Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 12:26:31 +0100 Subject: [PATCH 37/60] Bugfix: message() is null --- .../v/0.0.0/app/templates/Views/User/MailMessageView.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html index e2b035ae9..c65f8f7e4 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/MailMessageView.html @@ -31,7 +31,7 @@ </div> <!-- ko if: message --> - <div class="b-message" data-bind="i18nUpdate: message, css: message().lineAsCss(0)"> + <div class="b-message" data-bind="i18nUpdate: message, css: message()?.lineAsCss(0)"> <div class="message-fixed-button-toolbar"> <a class="btn btn-success buttonEdit fontastic hide-mobile" data-bind="visible: isDraftFolder(), command: editCommand">🖉</a> <div class="btn-group" style="margin-right: -8px; display: inline;"> From 4d2cd82d9570bc3c2edf787e7d89873f869241b9 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 12:30:11 +0100 Subject: [PATCH 38/60] Resolve #937 --- .../v/0.0.0/app/templates/Views/User/MailFolderListItem.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snappymail/v/0.0.0/app/templates/Views/User/MailFolderListItem.html b/snappymail/v/0.0.0/app/templates/Views/User/MailFolderListItem.html index 18e417992..b433f86e7 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/MailFolderListItem.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/MailFolderListItem.html @@ -6,7 +6,7 @@ <i data-bind="css: collapsedCss()"></i> <!-- ko text: name --><!-- /ko --> </a> - <!-- ko if: subFolders.length --> + <!-- ko if: subFolders().length --> <ul data-bind="template: { name: 'MailFolderListItem', foreach: subFolders }, css: { 'collapsed': collapsed() }"></ul> <!-- /ko --> </li> From aae3072209abe796c214c9aa6a6bc07a110fe4b0 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 14:07:38 +0100 Subject: [PATCH 39/60] Prepare cleanHtml() for new feature --- dev/Common/UtilsUser.js | 2 +- dev/Model/Message.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dev/Common/UtilsUser.js b/dev/Common/UtilsUser.js index 6f5176d97..1621f1b8b 100644 --- a/dev/Common/UtilsUser.js +++ b/dev/Common/UtilsUser.js @@ -276,7 +276,7 @@ populateMessageBody = (oMessage, popup) => { MessageUserStore.error(''); const messagesDom = MessageUserStore.bodiesDom(); if (messagesDom) { - let id = 'rl-msg-' + oMessage.hash.replace(/[^a-zA-Z0-9]/g, ''), + let id = 'rl-msg-' + oMessage.hash, body = elementById(id); if (body) { oMessage.body = body; diff --git a/dev/Model/Message.js b/dev/Model/Message.js index e19415c04..b45a66fde 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -25,6 +25,8 @@ import { LanguageStore } from 'Stores/Language'; import Remote from 'Remote/User/Fetch'; const + msgHtml = msg => cleanHtml(msg.html(), msg.attachments()), + toggleTag = (message, keyword) => { const lower = keyword.toLowerCase(), flags = message.flags, @@ -291,7 +293,7 @@ export class MessageModel extends AbstractModel { viewHtml() { const body = this.body; if (body && this.html()) { - let result = cleanHtml(this.html(), this.attachments()); + let result = msgHtml(this); this.hasExternals(result.hasExternals); this.hasImages(body.rlHasImages = !!result.hasExternals); @@ -460,7 +462,7 @@ export class MessageModel extends AbstractModel { ); return clone.innerHTML; } - let result = cleanHtml(this.html(), this.attachments()) + let result = msgHtml(this); return result.html || plainToHtml(this.plain()); } From 64818025e894edb79bc491525cd3d138bd77155a Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 14:54:38 +0100 Subject: [PATCH 40/60] Added feature to allow `<style>` in messages --- dev/Common/CSS.js | 82 +++++++++++++++++++ dev/Common/Html.js | 20 ++++- dev/Model/Message.js | 4 +- dev/Settings/User/General.js | 4 +- dev/Stores/User/Settings.js | 2 + .../0.0.0/app/libraries/RainLoop/Actions.php | 2 + .../app/libraries/RainLoop/Actions/User.php | 1 + .../v/0.0.0/app/localization/ar-SA/user.json | 1 + .../v/0.0.0/app/localization/bg-BG/user.json | 1 + .../v/0.0.0/app/localization/cs-CZ/user.json | 1 + .../v/0.0.0/app/localization/da-DK/user.json | 1 + .../v/0.0.0/app/localization/de-DE/user.json | 1 + .../v/0.0.0/app/localization/el-GR/user.json | 1 + .../v/0.0.0/app/localization/en-GB/user.json | 1 + .../v/0.0.0/app/localization/en/user.json | 1 + .../v/0.0.0/app/localization/es-ES/user.json | 1 + .../v/0.0.0/app/localization/et-EE/user.json | 1 + .../v/0.0.0/app/localization/eu/user.json | 1 + .../v/0.0.0/app/localization/fa-IR/user.json | 1 + .../v/0.0.0/app/localization/fi-FI/user.json | 1 + .../v/0.0.0/app/localization/fr-FR/user.json | 1 + .../v/0.0.0/app/localization/hu-HU/user.json | 1 + .../v/0.0.0/app/localization/id-ID/user.json | 1 + .../v/0.0.0/app/localization/is-IS/user.json | 1 + .../v/0.0.0/app/localization/it-IT/user.json | 1 + .../v/0.0.0/app/localization/ja-JP/user.json | 1 + .../v/0.0.0/app/localization/ko-KR/user.json | 1 + .../v/0.0.0/app/localization/lt-LT/user.json | 1 + .../v/0.0.0/app/localization/lv-LV/user.json | 1 + .../v/0.0.0/app/localization/nb-NO/user.json | 1 + .../v/0.0.0/app/localization/nl-NL/user.json | 1 + .../v/0.0.0/app/localization/pl-PL/user.json | 1 + .../v/0.0.0/app/localization/pt-BR/user.json | 1 + .../v/0.0.0/app/localization/pt-PT/user.json | 1 + .../v/0.0.0/app/localization/pt/user.json | 1 + .../v/0.0.0/app/localization/ro-RO/user.json | 1 + .../v/0.0.0/app/localization/ru-RU/user.json | 1 + .../v/0.0.0/app/localization/sk-SK/user.json | 1 + .../v/0.0.0/app/localization/sl-SI/user.json | 1 + .../v/0.0.0/app/localization/sv-SE/user.json | 1 + .../v/0.0.0/app/localization/tr-TR/user.json | 1 + .../v/0.0.0/app/localization/uk-UA/user.json | 1 + .../v/0.0.0/app/localization/vi-VN/user.json | 1 + .../v/0.0.0/app/localization/zh-CN/user.json | 1 + .../v/0.0.0/app/localization/zh-TW/user.json | 1 + .../templates/Views/User/SettingsGeneral.html | 7 ++ 46 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 dev/Common/CSS.js diff --git a/dev/Common/CSS.js b/dev/Common/CSS.js new file mode 100644 index 000000000..186e8a4f0 --- /dev/null +++ b/dev/Common/CSS.js @@ -0,0 +1,82 @@ + +export class CSS +{ + /* + Parses given css string, and returns css object + keys as selectors and values are css rules + eliminates all css comments before parsing + + @param source css string to be parsed + + @return object css + */ + parse(source) { + + const css = []; + + css.toString = () => css.reduce( + (ret, tmp) => + ret + tmp.selector + ' {\n' + + (tmp.type === 'media' ? tmp.subStyles.toString() : tmp.rules) + + '}\n' + , + '' + ); + + /** + * Given css array, parses it and then for every selector, + * prepends namespace to prevent css collision issues + */ + css.applyNamespace = (namespace) => css.forEach(obj => { + if (obj.type === 'media') { + obj.subStyles.applyNamespace(namespace); + } else { + obj.selector = obj.selector.split(',').map(selector => namespace + ' ' + selector).join(','); + } + }); + + if (source) { + source = source + // strip comments + .replace(/\/\*[\s\S]*?\*\/|<!--|-->/gi, '') + // strip import statements + .replace(/@import .*?;/gi , '') + // strip keyframe statements + .replace(/((@.*?keyframes [\s\S]*?){([\s\S]*?}\s*?)})/gi, ''); + + // unified regex to match css & media queries together + let unified = /((\s*?(?:\/\*[\s\S]*?\*\/)?\s*?@media[\s\S]*?){([\s\S]*?)}\s*?})|(([\s\S]*?){([\s\S]*?)})/gi, + arr; + + while (true) { + arr = unified.exec(source); + if (arr === null) { + break; + } + + let selector = arr[arr[2] === undefined ? 5 : 2].split('\r\n').join('\n').trim() + // Never have more than a single line break in a row + .replace(/\n+/, "\n"); + + // determine the type + if (selector.includes('@media')) { + // we have a media query + css.push({ + selector: selector, + type: 'media', + subStyles: this.parse(arr[3] + '\n}') //recursively parse media query inner css + }); + } else if (!selector.includes('@') && ![':root','html','body'].includes(selector)) { + // we have standard css + css.push({ + selector: selector, + rules: arr[6] + }); + } + } + } + + return css; + } + +} diff --git a/dev/Common/Html.js b/dev/Common/Html.js index 371445880..b84e739c1 100644 --- a/dev/Common/Html.js +++ b/dev/Common/Html.js @@ -1,4 +1,5 @@ import { createElement } from 'Common/Globals'; +import { CSS } from 'Common/CSS'; import { forEachObjectEntry, pInt } from 'Common/Utils'; import { SettingsUserStore } from 'Stores/User/Settings'; @@ -103,7 +104,7 @@ export const * @param {string} text * @returns {string} */ - cleanHtml = (html, oAttachments) => { + cleanHtml = (html, oAttachments, msgId) => { let aColor; const debug = false, // Config()->Get('debug', 'enable', false); @@ -132,7 +133,7 @@ export const }, allowedAttributes = [ // defaults - 'name', + 'name', 'class', 'dir', 'lang', 'style', 'title', 'background', 'bgcolor', 'alt', 'height', 'width', 'src', 'href', 'border', 'bordercolor', 'charset', 'direction', @@ -160,7 +161,7 @@ export const 'colspan', 'rowspan', 'headers' ], disallowedTags = [ - 'STYLE','SVG','SCRIPT','TITLE','LINK','BASE','META', + 'SVG','SCRIPT','TITLE','LINK','BASE','META', 'INPUT','OUTPUT','SELECT','BUTTON','TEXTAREA', 'BGSOUND','KEYGEN','SOURCE','OBJECT','EMBED','APPLET','IFRAME','FRAME','FRAMESET','VIDEO','AUDIO','AREA','MAP' // Not supported by <template> element @@ -171,6 +172,8 @@ export const ]; tpl.innerHTML = html + // Strip Microsoft comments + .replace(/<!--\[if[\s\S]*?endif\]-->/gi, '') // .replace(/<pre[^>]*>[\s\S]*?<\/pre>/gi, pre => pre.replace(/\n/g, '\n<br>')) // Not supported by <template> element // .replace(/<!doctype[^>]*>/gi, '') @@ -199,6 +202,17 @@ export const const name = oElement.tagName, oStyle = oElement.style; + if ('STYLE' === name) { + if (msgId) { + let css = new CSS().parse(oElement.textContent); + css.applyNamespace(msgId); + oElement.textContent = css; + } else { + oElement.remove(); + } + return; + } + // \MailSo\Base\HtmlUtils::ClearTags() if (disallowedTags.includes(name) || 'none' == oStyle.display diff --git a/dev/Model/Message.js b/dev/Model/Message.js index b45a66fde..8960306e9 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -25,7 +25,9 @@ import { LanguageStore } from 'Stores/Language'; import Remote from 'Remote/User/Fetch'; const - msgHtml = msg => cleanHtml(msg.html(), msg.attachments()), + msgHtml = msg => cleanHtml(msg.html(), msg.attachments(), + SettingsUserStore.allowStyles() ? '#rl-msg-' + msg.hash : '' + ), toggleTag = (message, keyword) => { const lower = keyword.toLowerCase(), diff --git a/dev/Settings/User/General.js b/dev/Settings/User/General.js index 07968de52..451d2b99b 100644 --- a/dev/Settings/User/General.js +++ b/dev/Settings/User/General.js @@ -44,7 +44,7 @@ export class UserSettingsGeneral extends AbstractViewSettings { ['layout', 'messageReadDelay', 'messagesPerPage', 'checkMailInterval', 'editorDefaultType', 'requestReadReceipt', 'requestDsn', 'requireTLS', 'pgpSign', 'pgpEncrypt', - 'viewHTML', 'viewImages', 'viewImagesWhitelist', 'removeColors', + 'viewHTML', 'viewImages', 'viewImagesWhitelist', 'removeColors', 'allowStyles', 'hideDeleted', 'listInlineAttachments', 'simpleAttachmentsList', 'collapseBlockquotes', 'maxBlockquotesLevel', 'useCheckboxesInList', 'listGrouped', 'useThreads', 'replySameFolder', 'msgDefaultAction', 'allowSpellcheck' ].forEach(name => this[name] = SettingsUserStore[name]); @@ -102,7 +102,7 @@ export class UserSettingsGeneral extends AbstractViewSettings { this.addSetting('Layout'); this.addSetting('MaxBlockquotesLevel'); - this.addSettings(['ViewHTML', 'ViewImages', 'ViewImagesWhitelist', 'HideDeleted', + this.addSettings(['ViewHTML', 'ViewImages', 'ViewImagesWhitelist', 'HideDeleted', 'AllowStyles', 'ListInlineAttachments', 'simpleAttachmentsList', 'UseCheckboxesInList', 'listGrouped', 'ReplySameFolder', 'requestReadReceipt', 'requestDsn', 'requireTLS', 'pgpSign', 'pgpEncrypt', 'allowSpellcheck', 'DesktopNotifications', 'SoundNotification', 'CollapseBlockquotes']); diff --git a/dev/Stores/User/Settings.js b/dev/Stores/User/Settings.js index 8ec19ff81..cd8b76188 100644 --- a/dev/Stores/User/Settings.js +++ b/dev/Stores/User/Settings.js @@ -19,6 +19,7 @@ export const SettingsUserStore = new class { viewImages: 0, viewImagesWhitelist: '', removeColors: 0, + allowStyles: 0, collapseBlockquotes: 1, maxBlockquotesLevel: 0, listInlineAttachments: 0, @@ -88,6 +89,7 @@ export const SettingsUserStore = new class { self.viewImages(SettingsGet('ViewImages')); self.viewImagesWhitelist(SettingsGet('ViewImagesWhitelist')); self.removeColors(SettingsGet('RemoveColors')); + self.allowStyles(SettingsGet('AllowStyles')); self.collapseBlockquotes(SettingsGet('CollapseBlockquotes')); self.maxBlockquotesLevel(SettingsGet('MaxBlockquotesLevel')); self.listInlineAttachments(SettingsGet('ListInlineAttachments')); 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 e2ab9a57e..070016feb 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -689,6 +689,7 @@ class Actions 'ViewImages' => 'ask', 'ViewImagesWhitelist' => '', 'RemoveColors' => (bool) $oConfig->Get('defaults', 'remove_colors', false), + 'AllowStyles' => false, 'ListInlineAttachments' => false, 'CollapseBlockquotes' => true, 'MaxBlockquotesLevel' => 0, @@ -801,6 +802,7 @@ class Actions $aResult['ViewImages'] = $oSettings->GetConf('ViewImages', $show_images ? 'always' : 'ask'); $aResult['ViewImagesWhitelist'] = $oSettings->GetConf('ViewImagesWhitelist', ''); $aResult['RemoveColors'] = (bool)$oSettings->GetConf('RemoveColors', $aResult['RemoveColors']); + $aResult['AllowStyles'] = (bool)$oSettings->GetConf('AllowStyles', $aResult['AllowStyles']); $aResult['ListInlineAttachments'] = (bool)$oSettings->GetConf('ListInlineAttachments', $aResult['ListInlineAttachments']); $aResult['CollapseBlockquotes'] = (bool)$oSettings->GetConf('CollapseBlockquotes', $aResult['CollapseBlockquotes']); $aResult['MaxBlockquotesLevel'] = (int)$oSettings->GetConf('MaxBlockquotesLevel', $aResult['MaxBlockquotesLevel']); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php index 605b65e88..64f291d8b 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php @@ -181,6 +181,7 @@ trait User $this->setSettingsFromParams($oSettings, 'ViewImages', 'string'); $this->setSettingsFromParams($oSettings, 'ViewImagesWhitelist', 'string'); $this->setSettingsFromParams($oSettings, 'RemoveColors', 'bool'); + $this->setSettingsFromParams($oSettings, 'AllowStyles', 'bool'); $this->setSettingsFromParams($oSettings, 'ListInlineAttachments', 'bool'); $this->setSettingsFromParams($oSettings, 'CollapseBlockquotes', 'bool'); $this->setSettingsFromParams($oSettings, 'MaxBlockquotesLevel', 'int'); diff --git a/snappymail/v/0.0.0/app/localization/ar-SA/user.json b/snappymail/v/0.0.0/app/localization/ar-SA/user.json index ddf4f3973..f043cad32 100644 --- a/snappymail/v/0.0.0/app/localization/ar-SA/user.json +++ b/snappymail/v/0.0.0/app/localization/ar-SA/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "الرسائل في الصفحة", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/bg-BG/user.json b/snappymail/v/0.0.0/app/localization/bg-BG/user.json index 3f4dd16a7..cd05a03b8 100644 --- a/snappymail/v/0.0.0/app/localization/bg-BG/user.json +++ b/snappymail/v/0.0.0/app/localization/bg-BG/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Съобщения на страница", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/cs-CZ/user.json b/snappymail/v/0.0.0/app/localization/cs-CZ/user.json index 6bbc5ca9a..51ca5bfe7 100644 --- a/snappymail/v/0.0.0/app/localization/cs-CZ/user.json +++ b/snappymail/v/0.0.0/app/localization/cs-CZ/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Zpráv na stranu", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/da-DK/user.json b/snappymail/v/0.0.0/app/localization/da-DK/user.json index 22063d963..e41fe4431 100644 --- a/snappymail/v/0.0.0/app/localization/da-DK/user.json +++ b/snappymail/v/0.0.0/app/localization/da-DK/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Meddelelser per side", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/de-DE/user.json b/snappymail/v/0.0.0/app/localization/de-DE/user.json index c878e4a6a..d2421af1a 100644 --- a/snappymail/v/0.0.0/app/localization/de-DE/user.json +++ b/snappymail/v/0.0.0/app/localization/de-DE/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "HTML gegenüber Klartext bevorzugen", "PREFER_HTML_INFO": "Nachrichten können manchmal in beiden Formaten empfangen werden. Diese Option setzt, ob die Nachricht standardmäßig in HTML oder als Klartext angezeigt wird.", "REMOVE_COLORS": "Entferne Hintergrund- und Textfarben aus der Nachricht", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Nachrichten pro Seite", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Nachricht als gelesen markieren nach", diff --git a/snappymail/v/0.0.0/app/localization/el-GR/user.json b/snappymail/v/0.0.0/app/localization/el-GR/user.json index b248941ca..55c23e253 100644 --- a/snappymail/v/0.0.0/app/localization/el-GR/user.json +++ b/snappymail/v/0.0.0/app/localization/el-GR/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Μηνύματα στη σελίδα", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/en-GB/user.json b/snappymail/v/0.0.0/app/localization/en-GB/user.json index f3eae6efe..933980260 100644 --- a/snappymail/v/0.0.0/app/localization/en-GB/user.json +++ b/snappymail/v/0.0.0/app/localization/en-GB/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Messages on page", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/en/user.json b/snappymail/v/0.0.0/app/localization/en/user.json index 182d931c0..ac2320991 100644 --- a/snappymail/v/0.0.0/app/localization/en/user.json +++ b/snappymail/v/0.0.0/app/localization/en/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Messages on page", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/es-ES/user.json b/snappymail/v/0.0.0/app/localization/es-ES/user.json index 9212d7c30..231796fe9 100644 --- a/snappymail/v/0.0.0/app/localization/es-ES/user.json +++ b/snappymail/v/0.0.0/app/localization/es-ES/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Eliminar el fondo y los colores del texto del mensaje", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Mensajes en página", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Marcar el mensaje como leído después de", diff --git a/snappymail/v/0.0.0/app/localization/et-EE/user.json b/snappymail/v/0.0.0/app/localization/et-EE/user.json index baf05ec6f..0b5474294 100644 --- a/snappymail/v/0.0.0/app/localization/et-EE/user.json +++ b/snappymail/v/0.0.0/app/localization/et-EE/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Lehel kuvatavate kirjade arv", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/eu/user.json b/snappymail/v/0.0.0/app/localization/eu/user.json index 7cbaff2d4..e1c65ebc9 100644 --- a/snappymail/v/0.0.0/app/localization/eu/user.json +++ b/snappymail/v/0.0.0/app/localization/eu/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Lehenetsi HTMLa testu arruntari", "PREFER_HTML_INFO": "Mezuak batzuetan bi formatuetan datoz. Aukera honekin HTML edo testu arrunt partea erakustea aukera dezakezu.", "REMOVE_COLORS": "Kendu atzealdeko irudia eta testuaren kolorean mezuaren gorputzetik", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Orrialdeko mezuak", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Markatu mezua irakurrita ondoren", diff --git a/snappymail/v/0.0.0/app/localization/fa-IR/user.json b/snappymail/v/0.0.0/app/localization/fa-IR/user.json index c9850b773..1ff3f2870 100644 --- a/snappymail/v/0.0.0/app/localization/fa-IR/user.json +++ b/snappymail/v/0.0.0/app/localization/fa-IR/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "پیام‌ها در صفحه", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/fi-FI/user.json b/snappymail/v/0.0.0/app/localization/fi-FI/user.json index ca37649eb..b8364eaf8 100644 --- a/snappymail/v/0.0.0/app/localization/fi-FI/user.json +++ b/snappymail/v/0.0.0/app/localization/fi-FI/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Viestiä sivulla", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/fr-FR/user.json b/snappymail/v/0.0.0/app/localization/fr-FR/user.json index 01a09b63c..f72275a3c 100644 --- a/snappymail/v/0.0.0/app/localization/fr-FR/user.json +++ b/snappymail/v/0.0.0/app/localization/fr-FR/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Préférer le HTML au texte brut", "PREFER_HTML_INFO": "Les messages arrivent parfois dans les deux formats. Cette option contrôle si vous voulez que la partie HTML ou la partie texte brut soit affichée.", "REMOVE_COLORS": "Supprimer les couleurs d'arrière-plan et de texte du message", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Messages par page", "CHECK_MAIL_INTERVAL": "Vérifier le messagerie à l'intervalle", "MARK_MESSAGE_READ_AFTER": "Marquer le message comme lu après", diff --git a/snappymail/v/0.0.0/app/localization/hu-HU/user.json b/snappymail/v/0.0.0/app/localization/hu-HU/user.json index 289c71247..9dcfd868d 100644 --- a/snappymail/v/0.0.0/app/localization/hu-HU/user.json +++ b/snappymail/v/0.0.0/app/localization/hu-HU/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Üzenetek egy oldalon", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Jelölje meg az üzenetet olvasottként", diff --git a/snappymail/v/0.0.0/app/localization/id-ID/user.json b/snappymail/v/0.0.0/app/localization/id-ID/user.json index f152a9e41..17c54d199 100644 --- a/snappymail/v/0.0.0/app/localization/id-ID/user.json +++ b/snappymail/v/0.0.0/app/localization/id-ID/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Pesan di halaman", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/is-IS/user.json b/snappymail/v/0.0.0/app/localization/is-IS/user.json index a54cb5e0f..5bada4e3f 100644 --- a/snappymail/v/0.0.0/app/localization/is-IS/user.json +++ b/snappymail/v/0.0.0/app/localization/is-IS/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Skilaboð á síðu", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/it-IT/user.json b/snappymail/v/0.0.0/app/localization/it-IT/user.json index ec6667e7c..164703d7d 100644 --- a/snappymail/v/0.0.0/app/localization/it-IT/user.json +++ b/snappymail/v/0.0.0/app/localization/it-IT/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Usa HTML predefinito anziché formato testo", "PREFER_HTML_INFO": "Il messaggio a volte contiene entrambi i formati. Questa opzione agisce sulla visualizzazione del messaggio in HTML o della versione in formato testo.", "REMOVE_COLORS": "Rimuovi lo sfondo e i colori del testo dal messaggio", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Messaggi per pagina", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Contrassegna il messaggio come letto dopo", diff --git a/snappymail/v/0.0.0/app/localization/ja-JP/user.json b/snappymail/v/0.0.0/app/localization/ja-JP/user.json index 043f08f4b..6472f2549 100644 --- a/snappymail/v/0.0.0/app/localization/ja-JP/user.json +++ b/snappymail/v/0.0.0/app/localization/ja-JP/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "プレーンテキストよりHTMLを優先する", "PREFER_HTML_INFO": "メッセージがHTMLとプレーンテキストの両方の形式を含むことがあります。このオプションはそのような場合にどちらの形式を表示するかを制御します。", "REMOVE_COLORS": "本文の文字色及び背景色の設定を無視する", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "1ページに表示する件数", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "メッセージが既読となるまでの秒数", diff --git a/snappymail/v/0.0.0/app/localization/ko-KR/user.json b/snappymail/v/0.0.0/app/localization/ko-KR/user.json index d05782151..a5f962ac5 100644 --- a/snappymail/v/0.0.0/app/localization/ko-KR/user.json +++ b/snappymail/v/0.0.0/app/localization/ko-KR/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "페이지 당 메시지 개수", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/lt-LT/user.json b/snappymail/v/0.0.0/app/localization/lt-LT/user.json index 6a7c67177..42b794081 100644 --- a/snappymail/v/0.0.0/app/localization/lt-LT/user.json +++ b/snappymail/v/0.0.0/app/localization/lt-LT/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Žinučių lape", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/lv-LV/user.json b/snappymail/v/0.0.0/app/localization/lv-LV/user.json index 190baf8ca..646af4515 100644 --- a/snappymail/v/0.0.0/app/localization/lv-LV/user.json +++ b/snappymail/v/0.0.0/app/localization/lv-LV/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Ziņojumi lapā", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/nb-NO/user.json b/snappymail/v/0.0.0/app/localization/nb-NO/user.json index 400cf8121..430b88ef1 100644 --- a/snappymail/v/0.0.0/app/localization/nb-NO/user.json +++ b/snappymail/v/0.0.0/app/localization/nb-NO/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Foretrekk HTML fremfor", "PREFER_HTML_INFO": "Meldinger kan ankomme i både HTML og ren tekst. Dette valget styrer hvilken av dem du vil se.", "REMOVE_COLORS": "Fjern bakgrunn og tekstfarger fra brødtekst", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Meldinger per side", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Marker melding som lest etter", diff --git a/snappymail/v/0.0.0/app/localization/nl-NL/user.json b/snappymail/v/0.0.0/app/localization/nl-NL/user.json index 852594508..e66ca2c57 100644 --- a/snappymail/v/0.0.0/app/localization/nl-NL/user.json +++ b/snappymail/v/0.0.0/app/localization/nl-NL/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Geef de voorkeur aan HTML boven platte tekst", "PREFER_HTML_INFO": "Berichten komen soms in beide formaten. Deze optie bepaalt of u het HTML-gedeelte of het platte tekstgedeelte wilt weergeven.", "REMOVE_COLORS": "Verwijder achtergrond- en tekstkleuren uit het bericht", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Berichten op pagina", "CHECK_MAIL_INTERVAL": "Controleer mail interval", "MARK_MESSAGE_READ_AFTER": "Bericht markeren als gelezen na", diff --git a/snappymail/v/0.0.0/app/localization/pl-PL/user.json b/snappymail/v/0.0.0/app/localization/pl-PL/user.json index d084a346e..679f83659 100644 --- a/snappymail/v/0.0.0/app/localization/pl-PL/user.json +++ b/snappymail/v/0.0.0/app/localization/pl-PL/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Wiadomości na stronę", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/pt-BR/user.json b/snappymail/v/0.0.0/app/localization/pt-BR/user.json index 6bc06a2a5..bedb39200 100644 --- a/snappymail/v/0.0.0/app/localization/pt-BR/user.json +++ b/snappymail/v/0.0.0/app/localization/pt-BR/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Mensagens por Página", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/pt-PT/user.json b/snappymail/v/0.0.0/app/localization/pt-PT/user.json index de5d7b18f..efdd3d5b1 100644 --- a/snappymail/v/0.0.0/app/localization/pt-PT/user.json +++ b/snappymail/v/0.0.0/app/localization/pt-PT/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Preferir HTML em vez de texto simples", "PREFER_HTML_INFO": "Por vezes as mensagens são recebidas em ambos os formatos. Esta opção determina se prefere apresentar o conteúdo em HTML ou em texto simples.", "REMOVE_COLORS": "Retirar imagens de fundo e coloração de texto do corpo das mensagens", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Mensagens por página", "CHECK_MAIL_INTERVAL": "Intervalo de verificação do correio", "MARK_MESSAGE_READ_AFTER": "Marcar mensagem como lida após", diff --git a/snappymail/v/0.0.0/app/localization/pt/user.json b/snappymail/v/0.0.0/app/localization/pt/user.json index de5d7b18f..efdd3d5b1 100644 --- a/snappymail/v/0.0.0/app/localization/pt/user.json +++ b/snappymail/v/0.0.0/app/localization/pt/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Preferir HTML em vez de texto simples", "PREFER_HTML_INFO": "Por vezes as mensagens são recebidas em ambos os formatos. Esta opção determina se prefere apresentar o conteúdo em HTML ou em texto simples.", "REMOVE_COLORS": "Retirar imagens de fundo e coloração de texto do corpo das mensagens", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Mensagens por página", "CHECK_MAIL_INTERVAL": "Intervalo de verificação do correio", "MARK_MESSAGE_READ_AFTER": "Marcar mensagem como lida após", diff --git a/snappymail/v/0.0.0/app/localization/ro-RO/user.json b/snappymail/v/0.0.0/app/localization/ro-RO/user.json index 7ce217e48..a1e440942 100644 --- a/snappymail/v/0.0.0/app/localization/ro-RO/user.json +++ b/snappymail/v/0.0.0/app/localization/ro-RO/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Mesaje pe o pagină", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/ru-RU/user.json b/snappymail/v/0.0.0/app/localization/ru-RU/user.json index 96f43b7cb..cc409eb78 100644 --- a/snappymail/v/0.0.0/app/localization/ru-RU/user.json +++ b/snappymail/v/0.0.0/app/localization/ru-RU/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Cообщений на одной странице", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/sk-SK/user.json b/snappymail/v/0.0.0/app/localization/sk-SK/user.json index 6e0ffa7a0..f07b3f8e3 100644 --- a/snappymail/v/0.0.0/app/localization/sk-SK/user.json +++ b/snappymail/v/0.0.0/app/localization/sk-SK/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Správ na stranu", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/sl-SI/user.json b/snappymail/v/0.0.0/app/localization/sl-SI/user.json index 5aa550585..60ced08aa 100644 --- a/snappymail/v/0.0.0/app/localization/sl-SI/user.json +++ b/snappymail/v/0.0.0/app/localization/sl-SI/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Sporočil na strani", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/sv-SE/user.json b/snappymail/v/0.0.0/app/localization/sv-SE/user.json index 349ddddad..1e8e39489 100644 --- a/snappymail/v/0.0.0/app/localization/sv-SE/user.json +++ b/snappymail/v/0.0.0/app/localization/sv-SE/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Meddelanden per sida", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Markera meddelandet som läst efter", diff --git a/snappymail/v/0.0.0/app/localization/tr-TR/user.json b/snappymail/v/0.0.0/app/localization/tr-TR/user.json index 90d81b06b..2187be45c 100644 --- a/snappymail/v/0.0.0/app/localization/tr-TR/user.json +++ b/snappymail/v/0.0.0/app/localization/tr-TR/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Messages on page", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/uk-UA/user.json b/snappymail/v/0.0.0/app/localization/uk-UA/user.json index a72c7db2f..14f4456a5 100644 --- a/snappymail/v/0.0.0/app/localization/uk-UA/user.json +++ b/snappymail/v/0.0.0/app/localization/uk-UA/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Повідомлень на одній сторінці", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/localization/vi-VN/user.json b/snappymail/v/0.0.0/app/localization/vi-VN/user.json index 29d01327b..809fd1088 100644 --- a/snappymail/v/0.0.0/app/localization/vi-VN/user.json +++ b/snappymail/v/0.0.0/app/localization/vi-VN/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Nghiêng về lựa chọn HTML hơn dạng chữ viết thường", "PREFER_HTML_INFO": "Đôi lúc thư nhận được sẽ ở cả hai dạng. Lựa chọn này cho phép bạn chỉnh phần HTML hay phần chữ viết thường trong thư bạn muốn hiển thị lên.", "REMOVE_COLORS": "Loại bỏ hình nền và màu chữ trong phần thân của thư", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "Số thư hiển thị trên trang", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Đánh dấu thư là để đọc sau", diff --git a/snappymail/v/0.0.0/app/localization/zh-CN/user.json b/snappymail/v/0.0.0/app/localization/zh-CN/user.json index 9e72dc2a6..bee3c9f60 100644 --- a/snappymail/v/0.0.0/app/localization/zh-CN/user.json +++ b/snappymail/v/0.0.0/app/localization/zh-CN/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "相比纯文本优先使用 HTML", "PREFER_HTML_INFO": "消息有时以两种格式传送。该选项控制您希望显示 HTML 部分还是纯文本部分。", "REMOVE_COLORS": "从消息中删除背景和文本颜色", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "每页消息数", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "将消息标记为已读", diff --git a/snappymail/v/0.0.0/app/localization/zh-TW/user.json b/snappymail/v/0.0.0/app/localization/zh-TW/user.json index 47d28fb14..414ade55e 100644 --- a/snappymail/v/0.0.0/app/localization/zh-TW/user.json +++ b/snappymail/v/0.0.0/app/localization/zh-TW/user.json @@ -403,6 +403,7 @@ "PREFER_HTML": "Prefer HTML to plain text", "PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.", "REMOVE_COLORS": "Remove background and text colors from message body", + "ALLOW_STYLES": "Allow <style> CSS", "MESSAGE_PER_PAGE": "封郵件每頁", "CHECK_MAIL_INTERVAL": "Check mail interval", "MARK_MESSAGE_READ_AFTER": "Mark message as read after", diff --git a/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html b/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html index 539be762a..d5c474dd0 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/SettingsGeneral.html @@ -206,6 +206,13 @@ value: removeColors } }"></div> + <div data-bind="component: { + name: 'Checkbox', + params: { + label: 'SETTINGS_GENERAL/ALLOW_STYLES', + value: allowStyles + } + }"></div> <div data-bind="component: { name: 'Checkbox', params: { From 5105f6d4c1d8db4ba7b413126575595c51857555 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 15:21:43 +0100 Subject: [PATCH 41/60] Improved handling of secure message `<style>` CSS class names --- dev/Common/CSS.js | 8 +++++--- dev/Common/Html.js | 29 ++++++++++++++++------------- dev/Model/Message.js | 4 +--- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/dev/Common/CSS.js b/dev/Common/CSS.js index 186e8a4f0..4e4d41879 100644 --- a/dev/Common/CSS.js +++ b/dev/Common/CSS.js @@ -27,11 +27,13 @@ export class CSS * Given css array, parses it and then for every selector, * prepends namespace to prevent css collision issues */ - css.applyNamespace = (namespace) => css.forEach(obj => { + css.applyNamespace = (namespace, prefix) => css.forEach(obj => { if (obj.type === 'media') { - obj.subStyles.applyNamespace(namespace); + obj.subStyles.applyNamespace(namespace, prefix); } else { - obj.selector = obj.selector.split(',').map(selector => namespace + ' ' + selector).join(','); + obj.selector = obj.selector.split(',').map(selector => + namespace + ' ' + selector.replace('.', '.'+prefix) + ).join(','); } }); diff --git a/dev/Common/Html.js b/dev/Common/Html.js index b84e739c1..b29f2e744 100644 --- a/dev/Common/Html.js +++ b/dev/Common/Html.js @@ -133,7 +133,7 @@ export const }, allowedAttributes = [ // defaults - 'name', 'class', + 'name', 'dir', 'lang', 'style', 'title', 'background', 'bgcolor', 'alt', 'height', 'width', 'src', 'href', 'border', 'bordercolor', 'charset', 'direction', @@ -171,6 +171,12 @@ export const 'A','B','EM','I','SPAN','STRONG' ]; + if (SettingsUserStore.allowStyles()) { + allowedAttributes.push('class'); + } else { + msgId = 0; + } + tpl.innerHTML = html // Strip Microsoft comments .replace(/<!--\[if[\s\S]*?endif\]-->/gi, '') @@ -205,8 +211,11 @@ export const if ('STYLE' === name) { if (msgId) { let css = new CSS().parse(oElement.textContent); - css.applyNamespace(msgId); + css.applyNamespace(msgId, 'msg-'); oElement.textContent = css; + if (SettingsUserStore.removeColors()) { + oElement.textContent = oElement.textContent.replace(/(background-)color:[^};]+/g, ''); + } } else { oElement.remove(); } @@ -225,29 +234,23 @@ export const oElement.remove(); return; } -/* - // Idea to allow CSS - if ('STYLE' === name) { - msgId = '#rl-msg-061eb4d647771be4185943ce91f0039d'; - oElement.textContent = oElement.textContent - .replace(/[^{}]+{/g, m => msgId + ' ' + m.replace(',', ', '+msgId+' ')) - .replace(/(background-)color:[^};]+/g, ''); - return; - } -*/ + const aAttrsForRemove = [], + className = oElement.className, hasAttribute = name => oElement.hasAttribute(name), getAttribute = name => hasAttribute(name) ? oElement.getAttribute(name).trim() : '', setAttribute = (name, value) => oElement.setAttribute(name, value), delAttribute = name => oElement.removeAttribute(name); - if ('mail-body' === oElement.className) { + if ('mail-body' === className) { forEachObjectEntry(tasks, (name, cb) => { if (hasAttribute(name)) { cb(getAttribute(name), oElement); delAttribute(name); } }); + } else if (msgId && className) { + oElement.className = className.replace(/(^|\s+)/g, '$1msg-'); } if (oElement.hasAttributes()) { diff --git a/dev/Model/Message.js b/dev/Model/Message.js index 8960306e9..0d62b73bc 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -25,9 +25,7 @@ import { LanguageStore } from 'Stores/Language'; import Remote from 'Remote/User/Fetch'; const - msgHtml = msg => cleanHtml(msg.html(), msg.attachments(), - SettingsUserStore.allowStyles() ? '#rl-msg-' + msg.hash : '' - ), + msgHtml = msg => cleanHtml(msg.html(), msg.attachments(), '#rl-msg-' + msg.hash), toggleTag = (message, keyword) => { const lower = keyword.toLowerCase(), From 492f1d4b106187ac58a212494c3df7fc51c0c5b7 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 15:32:12 +0100 Subject: [PATCH 42/60] v2.26.1 --- .../files/usr/local/include/application.ini | 2 +- CHANGELOG.md | 30 +++++++++++++++++++ README.md | 24 +++++++-------- integrations/cloudron/DESCRIPTION.md | 2 +- integrations/cloudron/Dockerfile | 2 +- .../nextcloud/snappymail/appinfo/info.xml | 2 +- integrations/virtualmin/snappymail.pl | 2 +- package.json | 2 +- .../v/0.0.0/app/localization/nl-NL/user.json | 2 +- 9 files changed, 49 insertions(+), 19 deletions(-) diff --git a/.docker/release/files/usr/local/include/application.ini b/.docker/release/files/usr/local/include/application.ini index fe4e7f469..9906cf551 100644 --- a/.docker/release/files/usr/local/include/application.ini +++ b/.docker/release/files/usr/local/include/application.ini @@ -316,5 +316,5 @@ dev_email = "" dev_password = "" [version] -current = "2.26.0" +current = "2.26.1" saved = "Sun, 18 Dec 2022 22:10:48 +0000" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d9ff98c4..7df7ae112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +## 2.26.1 – 2023-02-14 + +## Added +- Option to allow `<style>` in messages (beta) +- Message in new tab/window was missing BCC + +## Changed +- Don't clone message for viewing, so that there is instant interaction with messagelist item +- emailArrayToStringLineHelper() now filters addresses without email address +- Cleanup EmailModel and better email address parsing and handling +- Workaround "server connection error" + [#936](https://github.com/the-djmaze/snappymail/pull/936) +- Cleanup AbstractViewPopup handling +- Italian translation by @lota + [#948](https://github.com/the-djmaze/snappymail/pull/948) +- Replace Element.fromHTML() with createElement() + +## Fixed +- New subfolder not visible + [#937](https://github.com/the-djmaze/snappymail/pull/937) +- OpenPGP decrypted attachments `friendlySize` not a function +- Message @media print was broken due to new `display:flex` +- addressparser() didn't handle groups properly +- middleclick in messages list failed when messagesDom not yet initialized +- Cannot open messages that have an email address without an @ in the From header + [#950](https://github.com/the-djmaze/snappymail/pull/950) +- CSS don't display:flex when `[hidden]` +- Default theme handling by @Niveshkrishna + + ## 2.26.0 – 2023-02-10 ## Added diff --git a/README.md b/README.md index 4e7d63ca6..2dd32ac39 100644 --- a/README.md +++ b/README.md @@ -141,26 +141,26 @@ RainLoop 1.17 vs SnappyMail |js/* |RainLoop |Snappy | |--------------- |--------: |--------: | -|admin.js |2.170.153 | 81.589 | -|app.js |4.207.787 | 416.123 | +|admin.js |2.170.153 | 81.159 | +|app.js |4.207.787 | 414.949 | |boot.js | 868.735 | 2.711 | |libs.js | 658.812 | 192.392 | |sieve.js | 0 | 85.597 | |polyfills.js | 334.608 | 0 | |serviceworker.js | 0 | 285 | -|TOTAL |8.240.095 | 778.697 | +|TOTAL |8.240.095 | 777.093 | |js/min/* |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli | |--------------- |--------: |--------: |------: |------: |--------: |--------: | -|admin.min.js | 256.831 | 40.319 | 73.606 | 13.343 | 60.877 | 11.970 | -|app.min.js | 515.367 | 191.764 |139.456 | 64.128 |110.485 | 54.852 | +|admin.min.js | 256.831 | 40.198 | 73.606 | 13.281 | 60.877 | 11.916 | +|app.min.js | 515.367 | 190.317 |139.456 | 63.879 |110.485 | 54.792 | |boot.min.js | 84.659 | 1.600 | 26.998 | 940 | 23.643 | 767 | |libs.min.js | 584.772 | 91.434 |180.901 | 33.893 |155.182 | 30.383 | |sieve.min.js | 0 | 41.566 | 0 | 10.427 | 0 | 9.413 | |polyfills.min.js | 32.837 | 0 | 11.406 | 0 | 10.175 | 0 | -|TOTAL user |1.217.635 | 284.798 |358.761 | 98.961 |299.485 | 86.002 | -|TOTAL user+sieve |1.217.635 | 326.364 |358.761 |109.388 |299.485 | 95.415 | -|TOTAL admin | 959.099 | 133.353 |292.911 | 48.176 |249.877 | 43.120 | +|TOTAL user |1.217.635 | 283.351 |358.761 | 98.712 |299.485 | 85.942 | +|TOTAL user+sieve |1.217.635 | 324.917 |358.761 |109.139 |299.485 | 95.355 | +|TOTAL admin | 959.099 | 133.232 |292.911 | 48.114 |249.877 | 43.066 | For a user its around 70% smaller and faster than traditional RainLoop. @@ -189,12 +189,12 @@ For a user its around 70% smaller and faster than traditional RainLoop. |css/* |RainLoop |Snappy |RL gzip |SM gzip |SM brotli | |------------ |-------: |------: |------: |------: |--------: | -|app.css | 340.331 | 81.923 | 46.946 | 17.033 | 14.647 | -|app.min.css | 274.947 | 66.171 | 39.647 | 15.062 | 13.240 | +|app.css | 340.331 | 82.134 | 46.946 | 17.067 | 14.688 | +|app.min.css | 274.947 | 66.337 | 39.647 | 15.095 | 13.285 | |boot.css | | 1.326 | | 664 | 545 | |boot.min.css | | 1.071 | | 590 | 474 | -|admin.css | | 30.540 | | 6.984 | 6.093 | -|admin.min.css | | 24.738 | | 6.340 | 5.589 | +|admin.css | | 30.554 | | 6.984 | 6.085 | +|admin.min.css | | 24.752 | | 6.339 | 5.606 | ### PGP RainLoop uses the old OpenPGP.js v2 diff --git a/integrations/cloudron/DESCRIPTION.md b/integrations/cloudron/DESCRIPTION.md index eff25735e..7846c92d6 100644 --- a/integrations/cloudron/DESCRIPTION.md +++ b/integrations/cloudron/DESCRIPTION.md @@ -1,4 +1,4 @@ -This app packages SnappyMail <upstream>2.26.0</upstream>. +This app packages SnappyMail <upstream>2.26.1</upstream>. SnappyMail is a simple, modern, lightweight & fast web-based email client. diff --git a/integrations/cloudron/Dockerfile b/integrations/cloudron/Dockerfile index b16033f72..2df0e5681 100644 --- a/integrations/cloudron/Dockerfile +++ b/integrations/cloudron/Dockerfile @@ -4,7 +4,7 @@ RUN mkdir -p /app/code WORKDIR /app/code # If you change the extraction below, be sure to test on scaleway -VERSION=2.26.0 +VERSION=2.26.1 RUN wget https://github.com/the-djmaze/snappymail/releases/download/v${VERSION}/snappymail-${VERSION}.zip -O /tmp/snappymail.zip && \ unzip /tmp/snappymail.zip -d /app/code && \ rm /tmp/snappymail.zip && \ diff --git a/integrations/nextcloud/snappymail/appinfo/info.xml b/integrations/nextcloud/snappymail/appinfo/info.xml index ccfe3e70f..57f7cb080 100644 --- a/integrations/nextcloud/snappymail/appinfo/info.xml +++ b/integrations/nextcloud/snappymail/appinfo/info.xml @@ -3,7 +3,7 @@ <id>snappymail</id> <name>SnappyMail</name> <summary>SnappyMail Webmail</summary> - <version>2.26.0</version> + <version>2.26.1</version> <licence>agpl</licence> <author>SnappyMail, RainLoop Team, Nextgen-Networks, Tab Fitts, Nathan Kinkade, Pierre-Alain Bandinelli</author> <description><![CDATA[**Simple, modern, lightweight & fast web-based email client.** diff --git a/integrations/virtualmin/snappymail.pl b/integrations/virtualmin/snappymail.pl index 7055085eb..c718f86ab 100644 --- a/integrations/virtualmin/snappymail.pl +++ b/integrations/virtualmin/snappymail.pl @@ -20,7 +20,7 @@ return "SnappyMail Webmail is a browser-based multilingual IMAP client with an a # script_snappymail_versions() sub script_snappymail_versions { -return ( "2.26.0" ); +return ( "2.26.1" ); } sub script_snappymail_version_desc diff --git a/package.json b/package.json index 175339633..4ed9a3b4b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "title": "SnappyMail", "description": "Simple, modern & fast web-based email client", "private": true, - "version": "2.26.0", + "version": "2.26.1", "homepage": "https://snappymail.eu", "author": { "name": "DJ Maze", diff --git a/snappymail/v/0.0.0/app/localization/nl-NL/user.json b/snappymail/v/0.0.0/app/localization/nl-NL/user.json index e66ca2c57..98d79c36b 100644 --- a/snappymail/v/0.0.0/app/localization/nl-NL/user.json +++ b/snappymail/v/0.0.0/app/localization/nl-NL/user.json @@ -403,7 +403,7 @@ "PREFER_HTML": "Geef de voorkeur aan HTML boven platte tekst", "PREFER_HTML_INFO": "Berichten komen soms in beide formaten. Deze optie bepaalt of u het HTML-gedeelte of het platte tekstgedeelte wilt weergeven.", "REMOVE_COLORS": "Verwijder achtergrond- en tekstkleuren uit het bericht", - "ALLOW_STYLES": "Allow <style> CSS", + "ALLOW_STYLES": "CSS <style> toestaan", "MESSAGE_PER_PAGE": "Berichten op pagina", "CHECK_MAIL_INTERVAL": "Controleer mail interval", "MARK_MESSAGE_READ_AFTER": "Bericht markeren als gelezen na", From bd9e50f22241b067ce8a779b0ae1c4f922838765 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 16:51:51 +0100 Subject: [PATCH 43/60] Bugfix: new CSS `<style>` removing (background-)?color failed --- dev/Common/CSS.js | 84 -------------------------------------------- dev/Common/Html.js | 87 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 82 insertions(+), 89 deletions(-) delete mode 100644 dev/Common/CSS.js diff --git a/dev/Common/CSS.js b/dev/Common/CSS.js deleted file mode 100644 index 4e4d41879..000000000 --- a/dev/Common/CSS.js +++ /dev/null @@ -1,84 +0,0 @@ - -export class CSS -{ - /* - Parses given css string, and returns css object - keys as selectors and values are css rules - eliminates all css comments before parsing - - @param source css string to be parsed - - @return object css - */ - parse(source) { - - const css = []; - - css.toString = () => css.reduce( - (ret, tmp) => - ret + tmp.selector + ' {\n' - + (tmp.type === 'media' ? tmp.subStyles.toString() : tmp.rules) - + '}\n' - , - '' - ); - - /** - * Given css array, parses it and then for every selector, - * prepends namespace to prevent css collision issues - */ - css.applyNamespace = (namespace, prefix) => css.forEach(obj => { - if (obj.type === 'media') { - obj.subStyles.applyNamespace(namespace, prefix); - } else { - obj.selector = obj.selector.split(',').map(selector => - namespace + ' ' + selector.replace('.', '.'+prefix) - ).join(','); - } - }); - - if (source) { - source = source - // strip comments - .replace(/\/\*[\s\S]*?\*\/|<!--|-->/gi, '') - // strip import statements - .replace(/@import .*?;/gi , '') - // strip keyframe statements - .replace(/((@.*?keyframes [\s\S]*?){([\s\S]*?}\s*?)})/gi, ''); - - // unified regex to match css & media queries together - let unified = /((\s*?(?:\/\*[\s\S]*?\*\/)?\s*?@media[\s\S]*?){([\s\S]*?)}\s*?})|(([\s\S]*?){([\s\S]*?)})/gi, - arr; - - while (true) { - arr = unified.exec(source); - if (arr === null) { - break; - } - - let selector = arr[arr[2] === undefined ? 5 : 2].split('\r\n').join('\n').trim() - // Never have more than a single line break in a row - .replace(/\n+/, "\n"); - - // determine the type - if (selector.includes('@media')) { - // we have a media query - css.push({ - selector: selector, - type: 'media', - subStyles: this.parse(arr[3] + '\n}') //recursively parse media query inner css - }); - } else if (!selector.includes('@') && ![':root','html','body'].includes(selector)) { - // we have standard css - css.push({ - selector: selector, - rules: arr[6] - }); - } - } - } - - return css; - } - -} diff --git a/dev/Common/Html.js b/dev/Common/Html.js index b29f2e744..f1d2dd687 100644 --- a/dev/Common/Html.js +++ b/dev/Common/Html.js @@ -1,5 +1,4 @@ import { createElement } from 'Common/Globals'; -import { CSS } from 'Common/CSS'; import { forEachObjectEntry, pInt } from 'Common/Utils'; import { SettingsUserStore } from 'Stores/User/Settings'; @@ -89,6 +88,83 @@ const }); } return url; + }, + + /* + Parses given css string, and returns css object + keys as selectors and values are css rules + eliminates all css comments before parsing + + @param source css string to be parsed + + @return object css + */ + parseCSS = source => { + const css = []; + css.toString = () => css.reduce( + (ret, tmp) => + ret + tmp.selector + ' {\n' + + (tmp.type === 'media' ? tmp.subStyles.toString() : tmp.rules) + + '}\n' + , + '' + ); + /** + * Given css array, parses it and then for every selector, + * prepends namespace to prevent css collision issues + */ + css.applyNamespace = (namespace, prefix) => css.forEach(obj => { + if (obj.type === 'media') { + obj.subStyles.applyNamespace(namespace, prefix); + } else { + obj.selector = obj.selector.split(',').map(selector => + namespace + ' ' + selector.replace('.', '.'+prefix) + ).join(','); + } + }); + + if (source) { + source = source + // strip comments + .replace(/\/\*[\s\S]*?\*\/|<!--|-->/gi, '') + // strip import statements + .replace(/@import .*?;/gi , '') + // strip keyframe statements + .replace(/((@.*?keyframes [\s\S]*?){([\s\S]*?}\s*?)})/gi, ''); + + // unified regex to match css & media queries together + let unified = /((\s*?(?:\/\*[\s\S]*?\*\/)?\s*?@media[\s\S]*?){([\s\S]*?)}\s*?})|(([\s\S]*?){([\s\S]*?)})/gi, + arr; + + while (true) { + arr = unified.exec(source); + if (arr === null) { + break; + } + + let selector = arr[arr[2] === undefined ? 5 : 2].split('\r\n').join('\n').trim() + // Never have more than a single line break in a row + .replace(/\n+/, "\n"); + + // determine the type + if (selector.includes('@media')) { + // we have a media query + css.push({ + selector: selector, + type: 'media', + subStyles: parseCSS(arr[3] + '\n}') //recursively parse media query inner css + }); + } else if (!selector.includes('@') && ![':root','html','body'].includes(selector)) { + // we have standard css + css.push({ + selector: selector, + rules: arr[6] + }); + } + } + } + + return css; }; export const @@ -209,13 +285,14 @@ export const oStyle = oElement.style; if ('STYLE' === name) { - if (msgId) { - let css = new CSS().parse(oElement.textContent); + let css = msgId ? parseCSS(oElement.textContent) : []; + if (css.length) { css.applyNamespace(msgId, 'msg-'); - oElement.textContent = css; + css = css.toString(); if (SettingsUserStore.removeColors()) { - oElement.textContent = oElement.textContent.replace(/(background-)color:[^};]+/g, ''); + css = css.replace(/(background-)?color:[^};]+;?/g, ''); } + oElement.textContent = css; } else { oElement.remove(); } From 305c3b6a8662f5313d60477063dc21abf73c786e Mon Sep 17 00:00:00 2001 From: hguilbert <51283484+hguilbert@users.noreply.github.com> Date: Tue, 14 Feb 2023 18:24:25 +0100 Subject: [PATCH 44/60] Update user.json Some translation ... --- snappymail/v/0.0.0/app/localization/fr-FR/user.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/snappymail/v/0.0.0/app/localization/fr-FR/user.json b/snappymail/v/0.0.0/app/localization/fr-FR/user.json index f72275a3c..6162589f2 100644 --- a/snappymail/v/0.0.0/app/localization/fr-FR/user.json +++ b/snappymail/v/0.0.0/app/localization/fr-FR/user.json @@ -173,11 +173,11 @@ "CONTACTS": { "TAB_CONTACT": "Contact", "TAB_LOCATIONS": "Localisations", - "TAB_BUSINESS": "Métier", + "TAB_BUSINESS": "Professionel", "LABEL_ORGANIZATION": "Entreprise", - "LABEL_DEPARTMENT": "Department", + "LABEL_DEPARTMENT": "Service", "LABEL_TITLE": "Titre", - "TAB_CRYPTO": "Chiffre", + "TAB_CRYPTO": "Chiffrement", "ASK": "Demander", "NEVER": "Jamais", "ALWAYS": "Toujours", @@ -403,7 +403,7 @@ "PREFER_HTML": "Préférer le HTML au texte brut", "PREFER_HTML_INFO": "Les messages arrivent parfois dans les deux formats. Cette option contrôle si vous voulez que la partie HTML ou la partie texte brut soit affichée.", "REMOVE_COLORS": "Supprimer les couleurs d'arrière-plan et de texte du message", - "ALLOW_STYLES": "Allow <style> CSS", + "ALLOW_STYLES": "Autoriser <style> CSS", "MESSAGE_PER_PAGE": "Messages par page", "CHECK_MAIL_INTERVAL": "Vérifier le messagerie à l'intervalle", "MARK_MESSAGE_READ_AFTER": "Marquer le message comme lu après", From 9dd917894d6d4c61d4de4f6f2afa8c4e83de09b1 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 19:04:14 +0100 Subject: [PATCH 45/60] Resolve #960 --- dev/Knoin/AbstractViews.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dev/Knoin/AbstractViews.js b/dev/Knoin/AbstractViews.js index 035f6ba17..4e9774f99 100644 --- a/dev/Knoin/AbstractViews.js +++ b/dev/Knoin/AbstractViews.js @@ -59,6 +59,7 @@ export class AbstractViewPopup extends AbstractView super('Popups' + name, ViewTypePopup); this.keyScope.scope = name; this.modalVisible = ko.observable(false).extend({ rateLimit: 0 }); + this.close = () => this.modalVisible(false); addShortcut('escape,close', '', name, () => { if (this.modalVisible() && false !== this.onClose()) { this.close(); @@ -79,9 +80,6 @@ export class AbstractViewPopup extends AbstractView onHide() {} // Happens before animation transitionend afterHide() {} // Happens after animation transitionend */ - close() { - this.modalVisible(false); - } } AbstractViewPopup.showModal = function(params = []) { From d58cb75e534280a2642732bd83ef71953f5e86cd Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Tue, 14 Feb 2023 20:12:16 +0100 Subject: [PATCH 46/60] Resolve #958 --- snappymail/v/0.0.0/app/libraries/snappymail/http/csp.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/http/csp.php b/snappymail/v/0.0.0/app/libraries/snappymail/http/csp.php index 1e9761635..9b4542d44 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/http/csp.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/http/csp.php @@ -9,7 +9,7 @@ class CSP { public $base = ["'self'"], - $default = ["'self'"], + $default = ["'self'", 'data:'], // Knockout.js requires eval() for observable binding purposes // Safari < 15.4 does not support strict-dynamic // $script = ["'strict-dynamic'", "'unsafe-eval'"], From 163eb51ff6cfd98f99c1d675dcffc0ddebf5ccf5 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 15 Feb 2023 10:37:41 +0100 Subject: [PATCH 47/60] Resolve #964 --- snappymail/v/0.0.0/app/localization/es-ES/user.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snappymail/v/0.0.0/app/localization/es-ES/user.json b/snappymail/v/0.0.0/app/localization/es-ES/user.json index 231796fe9..43b49cbe2 100644 --- a/snappymail/v/0.0.0/app/localization/es-ES/user.json +++ b/snappymail/v/0.0.0/app/localization/es-ES/user.json @@ -19,7 +19,7 @@ "MOVE_TO": "Mover", "NAME": "Nombre", "NONE": "Ninguno", - "NOT_SPAM": "Correo no deseado", + "NOT_SPAM": "Es correo deseado", "USERNAME": "Nombre de usuario", "PASSWORD": "Contraseña", "REMEMBER": "Remember", @@ -27,7 +27,7 @@ "SAVE": "Guardar", "SAVE_CHANGES": "Save changes?", "SEARCH": "Buscar", - "SPAM": "Es correo deseado", + "SPAM": "Correo no deseado", "SUBJECT": "Asunto", "TEST": "Probar", "TO": "Para", From d11118a159a49d8eef135ab19784629ecbd78c7b Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 15 Feb 2023 10:54:12 +0100 Subject: [PATCH 48/60] Resolve #961 --- dev/Styles/User/Layout.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/Styles/User/Layout.less b/dev/Styles/User/Layout.less index f22835b85..e5fb91357 100644 --- a/dev/Styles/User/Layout.less +++ b/dev/Styles/User/Layout.less @@ -205,7 +205,7 @@ html.rl-mobile { top: 0; bottom: 0; width: 50vw; - z-index: 2; + z-index: 3; } &.rl-left-panel-disabled #rl-left { display: none; From 8058c8d2f6188792e9be7fd05be8c62c21381079 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 15 Feb 2023 11:26:21 +0100 Subject: [PATCH 49/60] Allow `<body>` element CSS as `.mail-body` --- dev/Common/Html.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dev/Common/Html.js b/dev/Common/Html.js index f1d2dd687..f5dadc991 100644 --- a/dev/Common/Html.js +++ b/dev/Common/Html.js @@ -144,7 +144,12 @@ const let selector = arr[arr[2] === undefined ? 5 : 2].split('\r\n').join('\n').trim() // Never have more than a single line break in a row - .replace(/\n+/, "\n"); + .replace(/\n+/, "\n") + // Remove :root and html + .split(/\s+/g).map(item => item + .replace(/^body$/, '.mail-body') + .replace(/^(:root|html)$/, '') + ).join(' ').trim(); // determine the type if (selector.includes('@media')) { @@ -154,7 +159,7 @@ const type: 'media', subStyles: parseCSS(arr[3] + '\n}') //recursively parse media query inner css }); - } else if (!selector.includes('@') && ![':root','html','body'].includes(selector)) { + } else if (selector && !selector.includes('@')) { // we have standard css css.push({ selector: selector, @@ -288,7 +293,7 @@ export const let css = msgId ? parseCSS(oElement.textContent) : []; if (css.length) { css.applyNamespace(msgId, 'msg-'); - css = css.toString(); + css = css.toString().replace('.msg-mail-body', '.mail-body'); if (SettingsUserStore.removeColors()) { css = css.replace(/(background-)?color:[^};]+;?/g, ''); } From 7a762e50c70b7530830c1b8099455121fcfa0bdf Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 15 Feb 2023 11:57:01 +0100 Subject: [PATCH 50/60] Resolve #957 --- dev/Common/Html.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/dev/Common/Html.js b/dev/Common/Html.js index f5dadc991..251b2ca5b 100644 --- a/dev/Common/Html.js +++ b/dev/Common/Html.js @@ -113,12 +113,13 @@ const * Given css array, parses it and then for every selector, * prepends namespace to prevent css collision issues */ - css.applyNamespace = (namespace, prefix) => css.forEach(obj => { + css.applyNamespace = namespace => css.forEach(obj => { if (obj.type === 'media') { - obj.subStyles.applyNamespace(namespace, prefix); + obj.subStyles.applyNamespace(namespace); } else { obj.selector = obj.selector.split(',').map(selector => - namespace + ' ' + selector.replace('.', '.'+prefix) + (namespace + ' .mail-body ' + selector.replace(/\./g, '.msg-')) + .replace(/\sbody/gi, '') ).join(','); } }); @@ -146,10 +147,7 @@ const // Never have more than a single line break in a row .replace(/\n+/, "\n") // Remove :root and html - .split(/\s+/g).map(item => item - .replace(/^body$/, '.mail-body') - .replace(/^(:root|html)$/, '') - ).join(' ').trim(); + .split(/\s+/g).map(item => item.replace(/^(:root|html)$/, '')).join(' ').trim(); // determine the type if (selector.includes('@media')) { @@ -292,8 +290,8 @@ export const if ('STYLE' === name) { let css = msgId ? parseCSS(oElement.textContent) : []; if (css.length) { - css.applyNamespace(msgId, 'msg-'); - css = css.toString().replace('.msg-mail-body', '.mail-body'); + css.applyNamespace(msgId); + css = css.toString(); if (SettingsUserStore.removeColors()) { css = css.replace(/(background-)?color:[^};]+;?/g, ''); } From ae1e6184c3afc008ce28a1e70caa3f603f376798 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 15 Feb 2023 11:57:30 +0100 Subject: [PATCH 51/60] Resolve #956 --- dev/Common/Html.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/Common/Html.js b/dev/Common/Html.js index 251b2ca5b..a9cc95753 100644 --- a/dev/Common/Html.js +++ b/dev/Common/Html.js @@ -356,7 +356,7 @@ export const delAttribute('width'); } value = oStyle.removeProperty('width'); - if (value && !oStyle.maxWidth) { + if (parseInt(value,10) && !oStyle.maxWidth) { oStyle.maxWidth = value; oStyle.width = '100%'; } From d629f42e4c5c34979be1f7479433c34b54c03e13 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 15 Feb 2023 12:03:21 +0100 Subject: [PATCH 52/60] x-script not used anymore --- snappymail/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php b/snappymail/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php index 1cfe9816a..6ae0e13ec 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Base/HtmlUtils.php @@ -97,7 +97,7 @@ abstract class HtmlUtils $oBody = $oDoc->getElementsByTagName('body')->item(0); $aRemoveTags = array( - 'svg', 'link', 'base', 'meta', 'title', 'x-script', 'script', 'bgsound', 'keygen', 'source', + 'svg', 'link', 'base', 'meta', 'title', 'script', 'bgsound', 'keygen', 'source', 'object', 'embed', 'applet', 'mocha', 'iframe', 'frame', 'frameset', 'video', 'audio', 'area', 'map', 'head', 'style' ); From 353fdda3e5f5fea8545e1315206c31a822e885ed Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 15 Feb 2023 12:03:51 +0100 Subject: [PATCH 53/60] Move some message checks to MailSo\Mime\Message --- .../app/libraries/MailSo/Mime/Message.php | 33 +++++++++++-------- .../libraries/RainLoop/Actions/Messages.php | 33 ++++--------------- 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Message.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Message.php index 2c37663ab..e13ddb553 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Message.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Message.php @@ -179,16 +179,19 @@ class Message extends Part public function SetInReplyTo(string $sInReplyTo) : self { - $this->aHeadersValue[Enumerations\Header::IN_REPLY_TO] = $sInReplyTo; - + $sInReplyTo = \trim($sInReplyTo); + if (\strlen($sInReplyTo)) { + $this->aHeadersValue[Enumerations\Header::IN_REPLY_TO] = $sInReplyTo; + } return $this; } public function SetReferences(string $sReferences) : self { - $this->aHeadersValue[Enumerations\Header::REFERENCES] = - \MailSo\Base\Utils::StripSpaces($sReferences); - + $sReferences = \MailSo\Base\Utils::StripSpaces($sReferences); + if (\strlen($sReferences)) { + $this->aHeadersValue[Enumerations\Header::REFERENCES] = $sReferences; + } return $this; } @@ -239,8 +242,9 @@ class Message extends Part public function SetTo(EmailCollection $oEmails) : self { - $this->aHeadersValue[Enumerations\Header::TO_] = $oEmails; - + if ($oEmails->count()) { + $this->aHeadersValue[Enumerations\Header::TO_] = $oEmails; + } return $this; } @@ -253,22 +257,25 @@ class Message extends Part public function SetReplyTo(EmailCollection $oEmails) : self { - $this->aHeadersValue[Enumerations\Header::REPLY_TO] = $oEmails; - + if ($oEmails->count()) { + $this->aHeadersValue[Enumerations\Header::REPLY_TO] = $oEmails; + } return $this; } public function SetCc(EmailCollection $oEmails) : self { - $this->aHeadersValue[Enumerations\Header::CC] = $oEmails; - + if ($oEmails->count()) { + $this->aHeadersValue[Enumerations\Header::CC] = $oEmails; + } return $this; } public function SetBcc(EmailCollection $oEmails) : self { - $this->aHeadersValue[Enumerations\Header::BCC] = $oEmails; - + if ($oEmails->count()) { + $this->aHeadersValue[Enumerations\Header::BCC] = $oEmails; + } return $this; } 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 303b6f645..4128692f6 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 @@ -989,10 +989,7 @@ trait Messages $oFrom = $oMessage->GetFrom(); $oMessage->RegenerateMessageId($oFrom ? $oFrom->GetDomain() : ''); - $oReplyTo = new \MailSo\Mime\EmailCollection($this->GetActionParam('replyTo', '')); - if ($oReplyTo->count()) { - $oMessage->SetReplyTo($oReplyTo); - } + $oMessage->SetReplyTo(new \MailSo\Mime\EmailCollection($this->GetActionParam('replyTo', ''))); if (!empty($this->GetActionParam('readReceiptRequest', 0))) { // Read Receipts Reference Main Account Email, Not Identities #147 @@ -1010,35 +1007,17 @@ trait Messages $oMessage->SetSubject($this->GetActionParam('subject', '')); - $oToEmails = new \MailSo\Mime\EmailCollection($this->GetActionParam('to', '')); - if ($oToEmails->count()) { - $oMessage->SetTo($oToEmails); - } - - $oCcEmails = new \MailSo\Mime\EmailCollection($this->GetActionParam('cc', '')); - if ($oCcEmails->count()) { - $oMessage->SetCc($oCcEmails); - } - - $oBccEmails = new \MailSo\Mime\EmailCollection($this->GetActionParam('bcc', '')); - if ($oBccEmails->count()) { - $oMessage->SetBcc($oBccEmails); - } + $oMessage->SetTo(new \MailSo\Mime\EmailCollection($this->GetActionParam('to', ''))); + $oMessage->SetCc(new \MailSo\Mime\EmailCollection($this->GetActionParam('cc', ''))); + $oMessage->SetBcc(new \MailSo\Mime\EmailCollection($this->GetActionParam('bcc', ''))); $aDraftInfo = $this->GetActionParam('draftInfo', null); if ($bWithDraftInfo && \is_array($aDraftInfo) && !empty($aDraftInfo[0]) && !empty($aDraftInfo[1]) && !empty($aDraftInfo[2])) { $oMessage->SetDraftInfo($aDraftInfo[0], $aDraftInfo[1], $aDraftInfo[2]); } - $sInReplyTo = $this->GetActionParam('inReplyTo', ''); - if (\strlen($sInReplyTo)) { - $oMessage->SetInReplyTo($sInReplyTo); - } - - $sReferences = $this->GetActionParam('references', ''); - if (\strlen($sReferences)) { - $oMessage->SetReferences($sReferences); - } + $oMessage->SetInReplyTo($this->GetActionParam('inReplyTo', '')); + $oMessage->SetReferences($this->GetActionParam('references', '')); $aFoundCids = array(); $aFoundDataURL = array(); From 9cfe164633422e0f1e12ed1c04f5841820ebf386 Mon Sep 17 00:00:00 2001 From: cm-schl <63400209+cm-schl@users.noreply.github.com> Date: Wed, 15 Feb 2023 17:57:59 +0100 Subject: [PATCH 54/60] Works now with additional accounts: The mail address is correctly set by ldap value. But plugin needs rework: it makes no sense to let the user use the non existing mail address "username@domain" used only by SnappyMail. Plugin should always use a looked up mail address --- .../ldap-mail-accounts/LdapMailAccounts.php | 23 ++++++++++----- plugins/ldap-mail-accounts/index.php | 28 +++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/plugins/ldap-mail-accounts/LdapMailAccounts.php b/plugins/ldap-mail-accounts/LdapMailAccounts.php index d2603f3b2..bbd7ec58c 100644 --- a/plugins/ldap-mail-accounts/LdapMailAccounts.php +++ b/plugins/ldap-mail-accounts/LdapMailAccounts.php @@ -113,7 +113,7 @@ class LdapMailAccounts $aAccounts = $oActions->GetAccounts($oAccount); - //Search for accounts with suffix " (LDAP)" at the end of the name that where created by this plugin and initially remove them from the + //Search for accounts with suffix " (LDAP)" at the end of the name that were created by this plugin and initially remove them from the //account array. This only removes the visibility but does not delete the config done by the user. So if a user looses access to a //mailbox the user will not see the account anymore but the configuration can be restored when the user regains access to it foreach($aAccounts as $key => $aAccount) @@ -129,31 +129,40 @@ class LdapMailAccounts $sUsername = $mailAddressResult->username; $sDomain = $mailAddressResult->domain; $sName = $mailAddressResult->name; + $sEmail = ""; + + //Create the email string - if disabled inside the config the email is a combination of the found username + @ + the found domain + if ($this->config->bool_overwrite_mail_address_additional_account) { + $sEmail = $mailAddressResult->mailAdditionalAccount; + } + else { + $sEmail = "$sUsername@$sDomain"; + } //Check if the domain of the found mail address is in the list of configured domains if ($oActions->DomainProvider()->Load($sDomain, true)) { //only execute if the found account isn't already in the list of additional accounts //and if the found account is different from the main account - if (!isset($aAccounts["$sUsername@$sDomain"]) && $oAccount->Email() !== "$sUsername@$sDomain") + if (!isset($aAccounts[$sEmail]) && $oAccount->Email() !== $sEmail) { //Try to login the user with the same password as the primary account has //if this fails the user will see the new mail addresses but will be asked for the correct password $sPass = $oAccount->IncPassword(); - $oNewAccount = RainLoop\Model\AdditionalAccount::NewInstanceFromCredentials($oActions, "$sUsername@$sDomain", $sUsername, $sPass); + $oNewAccount = RainLoop\Model\AdditionalAccount::NewInstanceFromCredentials($oActions, $sEmail, $sUsername, $sPass); - $aAccounts["$sUsername@$sDomain"] = $oNewAccount->asTokenArray($oAccount); + $aAccounts[$sEmail] = $oNewAccount->asTokenArray($oAccount); } //Always inject/update the found mailbox names into the array (also if the mailbox already existed) - if (isset($aAccounts["$sUsername@$sDomain"])) + if (isset($aAccounts[$sEmail])) { - $aAccounts["$sUsername@$sDomain"]['name'] = $sName . " (LDAP)"; + $aAccounts[$sEmail]['name'] = $sName . " (LDAP)"; } } else { - $this->logger->Write("Domain $sDomain is not part of configured domains in SnappyMail Admin Panel - mail address $sUsername@$sDomain will not be added.", \LOG_NOTICE, self::LOG_KEY); + $this->logger->Write("Domain $sDomain is not part of configured domains in SnappyMail Admin Panel - mail address $sEmail will not be added.", \LOG_NOTICE, self::LOG_KEY); } } diff --git a/plugins/ldap-mail-accounts/index.php b/plugins/ldap-mail-accounts/index.php index 711755499..422b227ba 100644 --- a/plugins/ldap-mail-accounts/index.php +++ b/plugins/ldap-mail-accounts/index.php @@ -32,6 +32,8 @@ class LdapMailAccountsPlugin extends AbstractPlugin public function Init(): void { $this->addHook("login.success", 'AddAdditionalLdapMailAccounts'); + $this->addHook('imap.before-login', 'MapImapCredentialsByLDAP'); + $this->addHook('smtp.before-login', 'MapSmtpCredentialsByLDAP'); } // Function gets called by RainLoop/Actions/User.php @@ -50,6 +52,32 @@ class LdapMailAccountsPlugin extends AbstractPlugin $oldapMailAccounts->AddLdapMailAccounts($oAccount); } + // Function gets called by Account.php + /** + * Overwrite the mailaddress of the account with the one found in LDAP by this plugin at IMAP login + * + * @param Account $oAccount + * @param ImapClient $oImapClient + * @param \MailSo\Imap\Settings $oSettings + */ + public function MapImapCredentialsByLDAP(\RainLoop\Model\Account $oAccount, \MailSo\Imap\ImapClient $oImapClient, \MailSo\Imap\Settings $oSettings) + { + //$oSettings->Login = $oAccount->IncLogin(); + } + + // Function gets called by Account.php + /** + * Overwrite the mailaddress of the account with the one found in LDAP by this plugin at SMTP login + * + * @param Account $oAccount + * @param SmtpClient $oSmtpClient + * @param \MailSo\Smtp\Settings $oSettings + */ + public function MapSmtpCredentialsByLDAP(\RainLoop\Model\Account $oAccount, \MailSo\Smtp\SmtpClient $oSmtpClient, \MailSo\Smtp\Settings $oSettings) + { + + } + /** * Defines the content of the plugin configuration page inside the Admin Panel of SnappyMail */ From b8c681d1a5831c86067e1856ac3e5adb7d3c38fc Mon Sep 17 00:00:00 2001 From: cm-schl <63400209+cm-schl@users.noreply.github.com> Date: Fri, 17 Feb 2023 12:01:39 +0100 Subject: [PATCH 55/60] Additional accounts now always get the mail address from ldap --- .../ldap-mail-accounts/LdapMailAccounts.php | 24 +++-------------- .../LdapMailAccountsConfig.php | 7 ++--- plugins/ldap-mail-accounts/index.php | 27 +++++++------------ 3 files changed, 16 insertions(+), 42 deletions(-) diff --git a/plugins/ldap-mail-accounts/LdapMailAccounts.php b/plugins/ldap-mail-accounts/LdapMailAccounts.php index bbd7ec58c..0d6cdebda 100644 --- a/plugins/ldap-mail-accounts/LdapMailAccounts.php +++ b/plugins/ldap-mail-accounts/LdapMailAccounts.php @@ -87,7 +87,6 @@ class LdapMailAccounts $this->config->field_domain, $this->config->bool_overwrite_mail_address_main_account, $this->config->field_mail_address_main_account, - $this->config->bool_overwrite_mail_address_additional_account, $this->config->field_mail_address_additional_account ); } @@ -129,15 +128,7 @@ class LdapMailAccounts $sUsername = $mailAddressResult->username; $sDomain = $mailAddressResult->domain; $sName = $mailAddressResult->name; - $sEmail = ""; - - //Create the email string - if disabled inside the config the email is a combination of the found username + @ + the found domain - if ($this->config->bool_overwrite_mail_address_additional_account) { - $sEmail = $mailAddressResult->mailAdditionalAccount; - } - else { - $sEmail = "$sUsername@$sDomain"; - } + $sEmail = $mailAddressResult->mailAdditionalAccount; //Check if the domain of the found mail address is in the list of configured domains if ($oActions->DomainProvider()->Load($sDomain, true)) @@ -149,7 +140,7 @@ class LdapMailAccounts //Try to login the user with the same password as the primary account has //if this fails the user will see the new mail addresses but will be asked for the correct password $sPass = $oAccount->IncPassword(); - + //After creating the accounts here $sUsername is used as username to login to the IMAP server (see Account.php) $oNewAccount = RainLoop\Model\AdditionalAccount::NewInstanceFromCredentials($oActions, $sEmail, $sUsername, $sPass); $aAccounts[$sEmail] = $oNewAccount->asTokenArray($oAccount); @@ -294,7 +285,6 @@ class LdapMailAccounts string $domainField, bool $overwriteMailMainAccount, string $mailAddressFieldMainAccount, - bool $overwriteMailAdditionalAccount, string $mailAddressFieldAdditionalAccount): array { $this->EnsureBound(); @@ -306,18 +296,12 @@ class LdapMailAccounts $this->logger->Write("Used ldap filter to search for additional mail accounts: $filter", \LOG_NOTICE, self::LOG_KEY); //Set together the attributes to search inside the LDAP - $ldapAttributes = ['dn', $usernameField, $nameField, $domainField]; + $ldapAttributes = ['dn', $usernameField, $nameField, $domainField, $mailAddressFieldAdditionalAccount]; if ($overwriteMailMainAccount) { \array_push($ldapAttributes, $mailAddressFieldMainAccount); } - if ($overwriteMailAdditionalAccount) - { - \array_push($ldapAttributes, $mailAddressFieldAdditionalAccount); - } - - $ldapResult = @ldap_search($this->ldap, $searchBase, $filter, $ldapAttributes); if (!$ldapResult) { $this->HandleLdapError("Fetch $objectClass"); @@ -346,7 +330,7 @@ class LdapMailAccounts $result->domain = $this->RemoveEventualLocalPart($result->domain); $result->mailMainAccount = $this->LdapGetAttribute($entry, $mailAddressFieldMainAccount, true, $overwriteMailMainAccount); - $result->mailAdditionalAccount = $this->LdapGetAttribute($entry, $mailAddressFieldAdditionalAccount, true, $overwriteMailAdditionalAccount); + $result->mailAdditionalAccount = $this->LdapGetAttribute($entry, $mailAddressFieldAdditionalAccount, true, true); $results[] = $result; } diff --git a/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php b/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php index ac08699e8..ab62ed58d 100644 --- a/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php +++ b/plugins/ldap-mail-accounts/LdapMailAccountsConfig.php @@ -18,10 +18,9 @@ class LdapMailAccountsConfig public const CONFIG_FIELD_USERNAME = "field_username"; public const CONFIG_SEARCH_STRING = "search_string"; public const CONFIG_FIELD_MAIL_DOMAIN = "field_domain"; + public const CONFIG_FIELD_MAIL_ADDRESS_ADDITIONAL_ACCOUNT = "field_mail_address_additional_account"; public const CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_MAIN_ACCOUNT = "bool_overwrite_mail_address_main_account"; public const CONFIG_FIELD_MAIL_ADDRESS_MAIN_ACCOUNT = "field_mail_address_main_account"; - public const CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_ADDITIONAL_ACCOUNT = "bool_overwrite_mail_address_additional_account"; - public const CONFIG_FIELD_MAIL_ADDRESS_ADDITIONAL_ACCOUNT = "field_mail_address_additional_account"; public $server; public $protocol; @@ -37,7 +36,6 @@ class LdapMailAccountsConfig public $field_mail_address_main_account; public $field_mail_address_additional_account; public $bool_overwrite_mail_address_main_account; - public $bool_overwrite_mail_address_additional_account; public static function MakeConfig(Plugin $config): LdapMailAccountsConfig { @@ -53,10 +51,9 @@ class LdapMailAccountsConfig $ldap->field_username = trim($config->Get("plugin", self::CONFIG_FIELD_USERNAME)); $ldap->search_string = trim($config->Get("plugin", self::CONFIG_SEARCH_STRING)); $ldap->field_domain = trim($config->Get("plugin", self::CONFIG_FIELD_MAIL_DOMAIN)); + $ldap->field_mail_address_additional_account = trim($config->Get("plugin", self::CONFIG_FIELD_MAIL_ADDRESS_ADDITIONAL_ACCOUNT)); $ldap->bool_overwrite_mail_address_main_account = $config->Get("plugin", self::CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_MAIN_ACCOUNT); $ldap->field_mail_address_main_account = trim($config->Get("plugin", self::CONFIG_FIELD_MAIL_ADDRESS_MAIN_ACCOUNT)); - $ldap->bool_overwrite_mail_address_additional_account = $config->Get("plugin", self::CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_ADDITIONAL_ACCOUNT); - $ldap->field_mail_address_additional_account = trim($config->Get("plugin", self::CONFIG_FIELD_MAIL_ADDRESS_ADDITIONAL_ACCOUNT)); return $ldap; } diff --git a/plugins/ldap-mail-accounts/index.php b/plugins/ldap-mail-accounts/index.php index 422b227ba..f08f01213 100644 --- a/plugins/ldap-mail-accounts/index.php +++ b/plugins/ldap-mail-accounts/index.php @@ -63,6 +63,7 @@ class LdapMailAccountsPlugin extends AbstractPlugin public function MapImapCredentialsByLDAP(\RainLoop\Model\Account $oAccount, \MailSo\Imap\ImapClient $oImapClient, \MailSo\Imap\Settings $oSettings) { //$oSettings->Login = $oAccount->IncLogin(); + //$this->Manager()->Actions()->Logger()->Write("E-Mail address: $oSettings->Login", \LOG_WARNING, "LDAP MAIL ACCOUNTS PLUGIN"); } // Function gets called by Account.php @@ -97,20 +98,6 @@ class LdapMailAccountsPlugin extends AbstractPlugin ->SetDefaultValue("mail"), ]); - $groupOverwriteAdditionalAccount = new \RainLoop\Plugins\PropertyCollection('Overwrite mail address of additional account'); - $groupOverwriteAdditionalAccount->exchangeArray([ - \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_BOOL_OVERWRITE_MAIL_ADDRESS_ADDITIONAL_ACCOUNT)->SetLabel('Enabled') - ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) - ->SetDefaultValue(false), - - \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_MAIL_ADDRESS_ADDITIONAL_ACCOUNT) - ->SetLabel("Mail address field for additional account") - ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) - ->SetDescription("The ldap field containing the mail address to use on the found additional mail account. - \nThe value found inside ldap will be used as mail address of the additional mail accounts created by this plugin.") - ->SetDefaultValue("mail"), - ]); - return [ \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_SERVER) ->SetLabel("LDAP Server URL") @@ -173,15 +160,21 @@ class LdapMailAccountsPlugin extends AbstractPlugin \nIf this field contains an email address, only the domain-part after the @ is used.") ->SetDefaultValue("mail"), + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_MAIL_ADDRESS_ADDITIONAL_ACCOUNT) + ->SetLabel("Mail address field for additional account") + ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) + ->SetDescription("The ldap field containing the mail address to use on the found additional mail account. + \nThe value found inside ldap will be used as mail address of the additional mail accounts created by this plugin. + \nIn most cases this could be the same ldap field as in \"Domain name field of additional account\"") + ->SetDefaultValue("mail"), + \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_NAME) ->SetLabel("Additional account name field") ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) ->SetDescription("The field containing the default sender name of the found additional mail account.") ->SetDefaultValue("displayName"), - $groupOverwriteMainAccount, - - $groupOverwriteAdditionalAccount, + $groupOverwriteMainAccount ]; } From 7948e86a07b69d25b361575001f60d6609868f08 Mon Sep 17 00:00:00 2001 From: cm-schl <63400209+cm-schl@users.noreply.github.com> Date: Fri, 17 Feb 2023 12:33:32 +0100 Subject: [PATCH 56/60] added comment --- plugins/ldap-mail-accounts/LdapMailAccounts.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/ldap-mail-accounts/LdapMailAccounts.php b/plugins/ldap-mail-accounts/LdapMailAccounts.php index 0d6cdebda..c45c38a4b 100644 --- a/plugins/ldap-mail-accounts/LdapMailAccounts.php +++ b/plugins/ldap-mail-accounts/LdapMailAccounts.php @@ -134,8 +134,11 @@ class LdapMailAccounts if ($oActions->DomainProvider()->Load($sDomain, true)) { //only execute if the found account isn't already in the list of additional accounts - //and if the found account is different from the main account - if (!isset($aAccounts[$sEmail]) && $oAccount->Email() !== $sEmail) + //and if the found account is different from the main account. + //The check if the address is different from the one of the main account when using the Nextcloud integration needs + //to be done twice: directly on the mail address (when Nextcloud is configured to log the user in by mail address) + //or on "$sUsername@$sDomain" for the case Nextcloud logs the user in to SnappyMail by his username and a default domain. + if (!isset($aAccounts[$sEmail]) && $oAccount->Email() !== $sEmail && $oAccount->Email() !== "$sUsername@$sDomain") { //Try to login the user with the same password as the primary account has //if this fails the user will see the new mail addresses but will be asked for the correct password From a8f6dab672d417e653e6a81038bc66284b52dbef Mon Sep 17 00:00:00 2001 From: cm-schl <63400209+cm-schl@users.noreply.github.com> Date: Fri, 3 Mar 2023 12:27:56 +0100 Subject: [PATCH 57/60] Working prototype. - Needs check if overwriting mail address is activated - Needs rework on ldap query function (looks up to much data) - Code cleanup is needed --- .../ldap-mail-accounts/LdapMailAccounts.php | 74 ++++++++++++++++++- plugins/ldap-mail-accounts/index.php | 29 +++++++- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/plugins/ldap-mail-accounts/LdapMailAccounts.php b/plugins/ldap-mail-accounts/LdapMailAccounts.php index c45c38a4b..5f8be4e48 100644 --- a/plugins/ldap-mail-accounts/LdapMailAccounts.php +++ b/plugins/ldap-mail-accounts/LdapMailAccounts.php @@ -46,15 +46,83 @@ class LdapMailAccounts $this->Connect(); } + + /** + * @inheritDoc + * + * AOverwrite the MainAccount mail address by looking up the new one in the ldap directory + * + * The ldap search string has to be configured in the plugin configuration of the extension (in the SnappyMail Admin Panel) + * + * @param string &$sEmail + * @param string &$sLogin + */ + public function overwriteEmail(&$sEmail, &$sLogin) + { + try { + $this->EnsureBound(); + } catch (LdapMailAccountsException $e) { + return false; // exceptions are only thrown from the handleerror function that does logging already + } + + // Try to get account information. IncLogin() returns the username of the user + // 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); + if ($oDomain->IncShortLogin()){ + $username = @ldap_escape($this->RemoveEventualDomainPart($sEmail), "", LDAP_ESCAPE_FILTER); + } + + $searchString = $this->config->search_string; + + // Replace placeholders inside the ldap search string with actual values + $searchString = str_replace("#USERNAME#", $username, $searchString); + $searchString = str_replace("#BASE_DN#", $this->config->base, $searchString); + + $this->logger->Write("ldap search string after replacement of placeholders: $searchString", \LOG_NOTICE, self::LOG_KEY); + + try { + $mailAddressResults = $this->FindLdapResults( + $this->config->field_search, + $searchString, + $this->config->base, + $this->config->objectclass, + $this->config->field_name, + $this->config->field_username, + $this->config->field_domain, + $this->config->bool_overwrite_mail_address_main_account, + $this->config->field_mail_address_main_account, + $this->config->field_mail_address_additional_account + ); + } + catch (LdapMailAccountsException $e) { + return false; // exceptions are only thrown from the handleerror function that does logging already + } + if (count($mailAddressResults) < 1) { + $this->logger->Write("Could not find user $username", \LOG_NOTICE, self::LOG_KEY); + return false; + } + + foreach($mailAddressResults as $mailAddressResult) + { + if($mailAddressResult->username === $username) { + //$sLogin is already set to be the same as $sEmail by function "resolveLoginCredentials" in /RainLoop/Actions/UserAuth.php + //that called this hook, so we just have to overwrite the mail address + $sEmail = $mailAddressResult->mailMainAccount; + } + } + } + /** * @inheritDoc * * Add additional mail accounts to the given primary account by looking up the ldap directory * - * The ldap lookup has to be configured in the plugin configuration of the extension (in the SnappyMail Admin Panel) + * The ldap search string has to be configured in the plugin configuration of the extension (in the SnappyMail Admin Panel) * * @param MainAccount $oAccount - * @return bool true if additional accounts have been added or no additional accounts where found in . false if an error occured + * @return bool true if additional accounts have been added or no additional accounts where found in ldap. false if an error occured */ public function AddLdapMailAccounts(MainAccount $oAccount): bool { @@ -64,7 +132,7 @@ class LdapMailAccounts return false; // exceptions are only thrown from the handleerror function that does logging already } - // Try to get account information. Login() returns the username of the user + // Try to get account information. IncLogin() returns the username of the user // and removes the domainname if this was configured inside the domain config. $username = @ldap_escape($oAccount->IncLogin(), "", LDAP_ESCAPE_FILTER); diff --git a/plugins/ldap-mail-accounts/index.php b/plugins/ldap-mail-accounts/index.php index f08f01213..c2b08d5fc 100644 --- a/plugins/ldap-mail-accounts/index.php +++ b/plugins/ldap-mail-accounts/index.php @@ -32,8 +32,30 @@ class LdapMailAccountsPlugin extends AbstractPlugin public function Init(): void { $this->addHook("login.success", 'AddAdditionalLdapMailAccounts'); - $this->addHook('imap.before-login', 'MapImapCredentialsByLDAP'); - $this->addHook('smtp.before-login', 'MapSmtpCredentialsByLDAP'); + //$this->addHook('imap.before-login', 'MapImapCredentialsByLDAP'); + //$this->addHook('smtp.before-login', 'MapSmtpCredentialsByLDAP'); + $this->addHook('login.credentials', 'overwriteMainAccountEmail'); + } + + // Function gets called by RainLoop/Actions/UserAuth.php + /** + * Overwrite the MainAccount mail address by looking up the new one in the ldap directory + * + * @param string &$sEmail + * @param string &$sLogin + */ + public function overwriteMainAccountEmail(&$sEmail, &$sLogin) + { + $this->Manager()->Actions()->Logger()->Write("Login DATA: login: $sLogin email: $sEmail", \LOG_WARNING, "LDAP MAIL ACCOUNTS PLUGIN"); + + // Set up config + $config = LdapMailAccountsConfig::MakeConfig($this->Config()); + + $oldapMailAccounts = new LdapMailAccounts($config, $this->Manager()->Actions()->Logger()); + + $oldapMailAccounts->overwriteEmail($sEmail, $sLogin); + + $this->Manager()->Actions()->Logger()->Write("Login DATA: login: $sLogin email: $sEmail", \LOG_WARNING, "LDAP MAIL ACCOUNTS PLUGIN"); } // Function gets called by RainLoop/Actions/User.php @@ -94,7 +116,8 @@ class LdapMailAccountsPlugin extends AbstractPlugin ->SetLabel("Mail address field for main account") ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) ->SetDescription("The ldap field containing the mail address to use on the SnappyMail main account. - \nThe value found inside ldap will overwrite the mail address of the SnappyMail main account (the account the user logged in at SnappyMail)") + \nThe value found inside ldap will overwrite the mail address of the SnappyMail main account (the account the user logged in at SnappyMail) + \nThe mail address used at login will still be used to login to the servers.") ->SetDefaultValue("mail"), ]); From 58b368552f7f64d2699e27f5dc54f394f795f8e9 Mon Sep 17 00:00:00 2001 From: cm-schl <63400209+cm-schl@users.noreply.github.com> Date: Tue, 7 Mar 2023 15:05:10 +0100 Subject: [PATCH 58/60] Overwrite of main address works - Cleanup of not used functions - Rewrite of FindLdapResults function to work in both cases: for additional accounts and main account --- .../ldap-mail-accounts/LdapMailAccounts.php | 33 ++++++------- plugins/ldap-mail-accounts/index.php | 47 ++++--------------- 2 files changed, 23 insertions(+), 57 deletions(-) diff --git a/plugins/ldap-mail-accounts/LdapMailAccounts.php b/plugins/ldap-mail-accounts/LdapMailAccounts.php index 5f8be4e48..066ee0a35 100644 --- a/plugins/ldap-mail-accounts/LdapMailAccounts.php +++ b/plugins/ldap-mail-accounts/LdapMailAccounts.php @@ -91,16 +91,15 @@ class LdapMailAccounts $this->config->field_name, $this->config->field_username, $this->config->field_domain, - $this->config->bool_overwrite_mail_address_main_account, + true, $this->config->field_mail_address_main_account, - $this->config->field_mail_address_additional_account ); } catch (LdapMailAccountsException $e) { return false; // exceptions are only thrown from the handleerror function that does logging already } if (count($mailAddressResults) < 1) { - $this->logger->Write("Could not find user $username", \LOG_NOTICE, self::LOG_KEY); + $this->logger->Write("Could not find user $username in LDAP! Overwriting of main mail address not possible.", \LOG_NOTICE, self::LOG_KEY); return false; } @@ -153,8 +152,7 @@ class LdapMailAccounts $this->config->field_name, $this->config->field_username, $this->config->field_domain, - $this->config->bool_overwrite_mail_address_main_account, - $this->config->field_mail_address_main_account, + false, $this->config->field_mail_address_additional_account ); } @@ -339,10 +337,8 @@ class LdapMailAccounts * @param string $nameField * @param string $usernameField * @param string $domainField - * @param bool $overwriteMailMainAccount - * @param string $mailAddressFieldMainAccount - * @param bool $overwriteMailAdditionalAccount - * @param string $mailAddressFieldAdditionalAccount + * @param bool $overwriteMailMainAccount true if the mail address of the main account should be looked up for overwriting. false if additional mail accounts should be searched + * @param string $mailAddressField The field containing the mail address (of main account or additional mail account) * @return LdapMailAccountResult[] * @throws LdapMailAccountsException */ @@ -355,8 +351,7 @@ class LdapMailAccounts string $usernameField, string $domainField, bool $overwriteMailMainAccount, - string $mailAddressFieldMainAccount, - string $mailAddressFieldAdditionalAccount): array + string $mailAddressField): array { $this->EnsureBound(); $nameField = strtolower($nameField); @@ -364,14 +359,10 @@ class LdapMailAccounts $domainField = strtolower($domainField); $filter = "(&(objectclass=$objectClass)($searchField=$searchString))"; - $this->logger->Write("Used ldap filter to search for additional mail accounts: $filter", \LOG_NOTICE, self::LOG_KEY); + $this->logger->Write("Used ldap filter to search for mail account(s): $filter", \LOG_NOTICE, self::LOG_KEY); //Set together the attributes to search inside the LDAP - $ldapAttributes = ['dn', $usernameField, $nameField, $domainField, $mailAddressFieldAdditionalAccount]; - if ($overwriteMailMainAccount) - { - \array_push($ldapAttributes, $mailAddressFieldMainAccount); - } + $ldapAttributes = ['dn', $usernameField, $nameField, $domainField, $mailAddressField]; $ldapResult = @ldap_search($this->ldap, $searchBase, $filter, $ldapAttributes); if (!$ldapResult) { @@ -400,8 +391,12 @@ class LdapMailAccounts $result->domain = $this->LdapGetAttribute($entry, $domainField, true, true); $result->domain = $this->RemoveEventualLocalPart($result->domain); - $result->mailMainAccount = $this->LdapGetAttribute($entry, $mailAddressFieldMainAccount, true, $overwriteMailMainAccount); - $result->mailAdditionalAccount = $this->LdapGetAttribute($entry, $mailAddressFieldAdditionalAccount, true, true); + if($overwriteMailMainAccount) { + $result->mailMainAccount = $this->LdapGetAttribute($entry, $mailAddressField, true, true); + } + else { + $result->mailAdditionalAccount = $this->LdapGetAttribute($entry, $mailAddressField, true, true); + } $results[] = $result; } diff --git a/plugins/ldap-mail-accounts/index.php b/plugins/ldap-mail-accounts/index.php index c2b08d5fc..345ad602a 100644 --- a/plugins/ldap-mail-accounts/index.php +++ b/plugins/ldap-mail-accounts/index.php @@ -32,8 +32,6 @@ class LdapMailAccountsPlugin extends AbstractPlugin public function Init(): void { $this->addHook("login.success", 'AddAdditionalLdapMailAccounts'); - //$this->addHook('imap.before-login', 'MapImapCredentialsByLDAP'); - //$this->addHook('smtp.before-login', 'MapSmtpCredentialsByLDAP'); $this->addHook('login.credentials', 'overwriteMainAccountEmail'); } @@ -46,16 +44,18 @@ class LdapMailAccountsPlugin extends AbstractPlugin */ public function overwriteMainAccountEmail(&$sEmail, &$sLogin) { - $this->Manager()->Actions()->Logger()->Write("Login DATA: login: $sLogin email: $sEmail", \LOG_WARNING, "LDAP MAIL ACCOUNTS PLUGIN"); +$this->Manager()->Actions()->Logger()->Write("Login DATA: login: $sLogin email: $sEmail", \LOG_WARNING, "LDAP MAIL ACCOUNTS PLUGIN"); - // Set up config - $config = LdapMailAccountsConfig::MakeConfig($this->Config()); + // Set up config + $config = LdapMailAccountsConfig::MakeConfig($this->Config()); - $oldapMailAccounts = new LdapMailAccounts($config, $this->Manager()->Actions()->Logger()); + if ($config->bool_overwrite_mail_address_main_account) + { + $oldapMailAccounts = new LdapMailAccounts($config, $this->Manager()->Actions()->Logger()); + $oldapMailAccounts->overwriteEmail($sEmail, $sLogin); + } - $oldapMailAccounts->overwriteEmail($sEmail, $sLogin); - - $this->Manager()->Actions()->Logger()->Write("Login DATA: login: $sLogin email: $sEmail", \LOG_WARNING, "LDAP MAIL ACCOUNTS PLUGIN"); +$this->Manager()->Actions()->Logger()->Write("Login DATA: login: $sLogin email: $sEmail", \LOG_WARNING, "LDAP MAIL ACCOUNTS PLUGIN"); } // Function gets called by RainLoop/Actions/User.php @@ -68,39 +68,10 @@ class LdapMailAccountsPlugin extends AbstractPlugin { // Set up config $config = LdapMailAccountsConfig::MakeConfig($this->Config()); - $oldapMailAccounts = new LdapMailAccounts($config, $this->Manager()->Actions()->Logger()); - $oldapMailAccounts->AddLdapMailAccounts($oAccount); } - // Function gets called by Account.php - /** - * Overwrite the mailaddress of the account with the one found in LDAP by this plugin at IMAP login - * - * @param Account $oAccount - * @param ImapClient $oImapClient - * @param \MailSo\Imap\Settings $oSettings - */ - public function MapImapCredentialsByLDAP(\RainLoop\Model\Account $oAccount, \MailSo\Imap\ImapClient $oImapClient, \MailSo\Imap\Settings $oSettings) - { - //$oSettings->Login = $oAccount->IncLogin(); - //$this->Manager()->Actions()->Logger()->Write("E-Mail address: $oSettings->Login", \LOG_WARNING, "LDAP MAIL ACCOUNTS PLUGIN"); - } - - // Function gets called by Account.php - /** - * Overwrite the mailaddress of the account with the one found in LDAP by this plugin at SMTP login - * - * @param Account $oAccount - * @param SmtpClient $oSmtpClient - * @param \MailSo\Smtp\Settings $oSettings - */ - public function MapSmtpCredentialsByLDAP(\RainLoop\Model\Account $oAccount, \MailSo\Smtp\SmtpClient $oSmtpClient, \MailSo\Smtp\Settings $oSettings) - { - - } - /** * Defines the content of the plugin configuration page inside the Admin Panel of SnappyMail */ From c732cec62fc8e9ecd0560ff5090ee3dccbfb0401 Mon Sep 17 00:00:00 2001 From: cm-schl <63400209+cm-schl@users.noreply.github.com> Date: Tue, 7 Mar 2023 15:31:09 +0100 Subject: [PATCH 59/60] Clearer description whats username for in the config --- plugins/ldap-mail-accounts/LdapMailAccounts.php | 8 ++++---- plugins/ldap-mail-accounts/index.php | 11 +++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/plugins/ldap-mail-accounts/LdapMailAccounts.php b/plugins/ldap-mail-accounts/LdapMailAccounts.php index 066ee0a35..e68e9e179 100644 --- a/plugins/ldap-mail-accounts/LdapMailAccounts.php +++ b/plugins/ldap-mail-accounts/LdapMailAccounts.php @@ -62,7 +62,7 @@ class LdapMailAccounts try { $this->EnsureBound(); } catch (LdapMailAccountsException $e) { - return false; // exceptions are only thrown from the handleerror function that does logging already + return false; // exceptions are only thrown from the handle error function that does logging already } // Try to get account information. IncLogin() returns the username of the user @@ -96,7 +96,7 @@ class LdapMailAccounts ); } catch (LdapMailAccountsException $e) { - return false; // exceptions are only thrown from the handleerror function that does logging already + return false; // exceptions are only thrown from the handle error function that does logging already } if (count($mailAddressResults) < 1) { $this->logger->Write("Could not find user $username in LDAP! Overwriting of main mail address not possible.", \LOG_NOTICE, self::LOG_KEY); @@ -128,7 +128,7 @@ class LdapMailAccounts try { $this->EnsureBound(); } catch (LdapMailAccountsException $e) { - return false; // exceptions are only thrown from the handleerror function that does logging already + return false; // exceptions are only thrown from the handle error function that does logging already } // Try to get account information. IncLogin() returns the username of the user @@ -157,7 +157,7 @@ class LdapMailAccounts ); } catch (LdapMailAccountsException $e) { - return false; // exceptions are only thrown from the handleerror function that does logging already + return false; // exceptions are only thrown from the handle error function that does logging already } if (count($mailAddressResults) < 1) { $this->logger->Write("Could not find user $username", \LOG_NOTICE, self::LOG_KEY); diff --git a/plugins/ldap-mail-accounts/index.php b/plugins/ldap-mail-accounts/index.php index 345ad602a..8d28d1e78 100644 --- a/plugins/ldap-mail-accounts/index.php +++ b/plugins/ldap-mail-accounts/index.php @@ -139,11 +139,14 @@ $this->Manager()->Actions()->Logger()->Write("Login DATA: login: $sLogin email: ->SetDefaultValue("uid=#USERNAME#"), \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_USERNAME) - ->SetLabel("Username field of additional account") + ->SetLabel("Username field") ->SetType(RainLoop\Enumerations\PluginPropertyType::STRING) - ->SetDescription("The field containing the username of the found additional mail account. - \nThis username gets used by SnappyMail to login to the additional mail account. - \nIf this field contains an email address, only the local-part before the @ is used.") + ->SetDescription("Used when searching for additional accounts or when overwriting the mail address of the main account. + \nThe field containing the username of the mail account. + \nWhen looking up additional accounts: + \nIf this field contains an email address, only the local-part before the @ is used. The domain part is retrieved configuring the field below. This username gets used by SnappyMail to login to the additional mail account + \nWhen overwriting the main account mail address: + \nThe username from SnappyMail login gets used to search an LDAP entry containig a field with the same username.") ->SetDefaultValue("uid"), \RainLoop\Plugins\Property::NewInstance(LdapMailAccountsConfig::CONFIG_FIELD_MAIL_DOMAIN) From 2d4e72805a1f02e1f6abbabcb2ea4727775d8f61 Mon Sep 17 00:00:00 2001 From: cm-schl <63400209+cm-schl@users.noreply.github.com> Date: Tue, 14 Mar 2023 14:37:37 +0100 Subject: [PATCH 60/60] Updated readme.md --- plugins/ldap-mail-accounts/README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/plugins/ldap-mail-accounts/README.md b/plugins/ldap-mail-accounts/README.md index 28176c2e8..8f7417e8b 100644 --- a/plugins/ldap-mail-accounts/README.md +++ b/plugins/ldap-mail-accounts/README.md @@ -4,6 +4,10 @@ This plugin can be used to add additional mail accounts to SnappyMail when a user logs in successfully. The list of additional accounts is retrieved by a ldap query that can be configured inside the plugin settings.\ On a successful login the username of the SnappyMail user is passed to the plugin and will be searched in the ldap. If additional mail accounts are found, the username and domain-part of those will be used to add the new mail account. The plugin tries to log in the user with the same password used to login to SnappyMail - if this fails SnappyMail asks the user to insert his credentials. +Version 2.0.0 changes the way additional mail accounts get their e-mail address: the mail address connected with additional mail accounts is now always the address found inside the ldap. +Now it is also possible to overwrite the mail address of the main account: if a user logs into SnappyMail with a username and SnappyMail added the configured default domain the mail address of the main account could have been some not existing address like "username@default-domain.com". This could have happend when using the Nextcloud SnappyMail integration that offers an automatic login using the Nextcloud username. +The plugin now can be configured to overwrite the username or mail address used at login with a mail address found inside ldap. + ### Configuration - Install and activate the plugin using the SnappyMail Admin Panel -> menu Extensions. - Click on the gear symbol beside the plugin to open the configration dialog. @@ -11,7 +15,9 @@ On a successful login the username of the SnappyMail user is passed to the plugi - The fields `Object class`, `Base DN`, `Search field` and `LDAP search string` are used to put together a ldap search filter which will return the additional mail accounts of a user:\ `(&(objectclass=<YOUR OBJECT CLASS>)(<YOUR SEARCH FIELD>=<YOUR SEARCH STRING>))`.\ This filter will be executed on the `Base DN` you have defined. `LDAP search string` can contain the placeholders `#USERNAME#` (will be replaced with the username the user logged in to Snappymail) and `#BASE_DN#` (will be replaced with the value you inserted into the field `Base DN` inside the plugin settings). This will allow you to create more complex search strings like `uid=#USERNAME#`. - - `Username field of additional account`, `Domain name field of additional account` and `Additional account name field` are used to define the ldap attributes to read when the ldap search was successful. For example insert `mail` into the `Username field of additional account` and the `Domain name field of additional account` to use the [local-part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the mail address as username and the [domain-part](https://en.wikipedia.org/wiki/Email_address#Domain) as domain for the additional account.\ - `Username field of additional account` and `Domain name field of additional account` before use get checked by the plugin if they contain a mail address and if true only the local-part or domain-part is returned. If no `@` is found the content of the found ldap attribute is returned without modification. This can be usefull if your user should login with something different than the mail address (a username that is diffrent from the local-part of the mail address). + - `Username field`, `Domain name field of additional account`, `Mail address field for additional account` and `Additional account name field` are used to define the ldap attributes to read when the ldap search was successful. For example insert `mail` into the `Username field` and the `Domain name field of additional account` to use the [local-part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the mail address as username and the [domain-part](https://en.wikipedia.org/wiki/Email_address#Domain) as domain for the additional account.\ + `Username field` and `Domain name field of additional account` before use get checked by the plugin if they contain a mail address and if true only the local-part or domain-part is returned. If no `@` is found the content of the found ldap attribute is returned without modification. This can be usefull if your user should login with something different than the mail address (a username that is diffrent from the local-part of the mail address). -**Important:** keep in mind, that SnappyMail normally needs a mail address as username. In some special circumstances (login with an ldap username, not a mail address) this can mean that you have to configure the plugin to put together a 'fake' mail address like `<LDAP-USERNAME>@<YOUR-DOMAIN>`. + Section `Overwrite mail address of main account` can be used to overwrite the username or mail address used at login with a value found in ldap. If activated, the username or mail address used at login will be looked up inside the `Username field` in ldap (for details see how a search for additional accounts is made). If the username is found, the value of the field `Mail address field for main account` will be used to overwrite the mail address of the main account. + +**Important:** SnappyMail normally needs a mail address as username. This plugin handles some special circumstances (login with an ldap username, not a mail address) so that you can login to your IMAP server with the ldap username but send mails with a mail address connected to this ldap user.
- +