From a24dade801fb4795e48fdecade6a12ab11b35547 Mon Sep 17 00:00:00 2001 From: cm-schl <63400209+cm-schl@users.noreply.github.com> Date: Wed, 8 Feb 2023 15:38:47 +0100 Subject: [PATCH 01/63] Update index.php --- plugins/login-override/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/login-override/index.php b/plugins/login-override/index.php index aae5c29f7..4fce7b776 100644 --- a/plugins/login-override/index.php +++ b/plugins/login-override/index.php @@ -22,7 +22,7 @@ class LoginOverridePlugin extends \RainLoop\Plugins\AbstractPlugin $sMapping = \trim($this->Config()->Get('plugin', 'email_mapping', '')); if (!empty($sMapping)) { $aList = \preg_split('/\\R/', $sMapping); - foreach ($aList as $line) { + foreach ($aList as $sLine) { $aData = \explode(':', $sLine, 2); if (!empty($aData[1]) && $sEmail === \trim($aData[0])) { $sEmail = \trim($aData[1]); From b448b8661de11bf7b0a6d1c870a507b8b9f9f90c Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 8 Feb 2023 17:39:30 +0100 Subject: [PATCH 02/63] Properly fix #928 by redesign using `display: flex` --- dev/App/User.js | 2 +- dev/Common/UtilsUser.js | 19 +---- dev/Screen/User/MailBox.js | 24 +----- dev/Styles/User/Layout.less | 116 ++++----------------------- dev/Styles/User/MessageList.less | 23 ++++++ dev/Styles/User/MessageView.less | 31 +++++++ dev/View/User/MailBox/MessageList.js | 20 +++++ 7 files changed, 94 insertions(+), 141 deletions(-) diff --git a/dev/App/User.js b/dev/App/User.js index 10bb3cb9e..da75c23eb 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -251,7 +251,7 @@ export class AppUser extends AbstractApp { setTimeout(() => { const left = elementById('rl-left'), fToggle = () => - setLayoutResizer(left, null, ClientSideKeyNameFolderListSize, + setLayoutResizer(left, ClientSideKeyNameFolderListSize, (ThemeStore.isMobile() || leftPanelDisabled()) ? 0 : 'Width'); if (left) { fToggle(); diff --git a/dev/Common/UtilsUser.js b/dev/Common/UtilsUser.js index 6588c4f59..e8fb0991a 100644 --- a/dev/Common/UtilsUser.js +++ b/dev/Common/UtilsUser.js @@ -196,37 +196,24 @@ showMessageComposer = (params = []) => rl.app.showMessageComposer(params); }, -setLayoutResizer = (source, target, sClientSideKeyName, mode) => +setLayoutResizer = (source, sClientSideKeyName, mode) => { if (source.layoutResizer && source.layoutResizer.mode != mode) { - target?.removeAttribute('style'); source.removeAttribute('style'); } source.observer?.disconnect(); // source.classList.toggle('resizable', mode); if (mode) { - const length = Local.get(sClientSideKeyName + mode) || SettingsGet('Resizer' + sClientSideKeyName + mode), - setTargetPos = mode => { - let value; - if ('Width' == mode) { - value = source.offsetWidth; - target && (target.style.left = value + 'px'); - } else { - value = source.offsetHeight; - target && (target.style.top = value + 'px'); - } - return value; - }; + const length = Local.get(sClientSideKeyName + mode) || SettingsGet('Resizer' + sClientSideKeyName + mode); if (length) { source.style[mode.toLowerCase()] = length + 'px'; - setTargetPos(mode); } if (!source.layoutResizer) { const resizer = createElement('div', {'class':'resizer'}), save = (data => Remote.saveSettings(0, data)).debounce(500), size = {}, store = () => { - const value = setTargetPos(resizer.mode), + const value = ('Width' == resizer.mode) ? source.offsetWidth : source.offsetHeight, prop = resizer.key + resizer.mode; (value == Local.get(prop)) || Local.set(prop, value); (value == SettingsGet('Resizer' + prop)) || save({['Resizer' + prop]: value}); diff --git a/dev/Screen/User/MailBox.js b/dev/Screen/User/MailBox.js index f3e47823d..3ab6e80ee 100644 --- a/dev/Screen/User/MailBox.js +++ b/dev/Screen/User/MailBox.js @@ -1,11 +1,9 @@ import { Scope } from 'Common/Enums'; -import { Layout, ClientSideKeyNameMessageListSize } from 'Common/EnumsUser'; -import { doc, createElement, leftPanelDisabled, Settings, elementById } from 'Common/Globals'; +import { doc, createElement, leftPanelDisabled, Settings } from 'Common/Globals'; import { pString, pInt } from 'Common/Utils'; -import { setLayoutResizer, moveAction } from 'Common/UtilsUser'; +import { moveAction } from 'Common/UtilsUser'; import { getFolderFromCacheList, getFolderFullName, getFolderInboxName } from 'Common/Cache'; import { i18n, initOnStartOrLangChange } from 'Common/Translator'; -import { SettingsUserStore } from 'Stores/User/Settings'; import { AppUserStore } from 'Stores/User/App'; import { AccountUserStore } from 'Stores/User/Account'; @@ -113,24 +111,6 @@ export class MailBoxUserScreen extends AbstractScreen { * @returns {void} */ onBuild() { - setTimeout(() => { - // initMailboxLayoutResizer - const top = elementById('V-MailMessageList'), - bottom = elementById('V-MailMessageView'), - fToggle = () => { - let layout = SettingsUserStore.layout(); - setLayoutResizer(top, bottom, ClientSideKeyNameMessageListSize, - (ThemeStore.isMobile() || Layout.NoPreview === layout) - ? 0 - : (Layout.SidePreview === layout ? 'Width' : 'Height') - ); - }; - if (top && bottom) { - fToggle(); - addEventListener('rl-layout', fToggle); - } - }, 1); - doc.addEventListener('click', event => event.target.closest('#rl-right') && moveAction(false) ); diff --git a/dev/Styles/User/Layout.less b/dev/Styles/User/Layout.less index 87a38d301..3516bac25 100644 --- a/dev/Styles/User/Layout.less +++ b/dev/Styles/User/Layout.less @@ -15,10 +15,12 @@ } #rl-right { + display: flex; flex-grow: 1; - max-width: 100vw; - position: relative; - z-index: 1; + width: 20%; +} +.rl-bottom-preview-pane #rl-right { + flex-direction: column; } /* @@ -58,82 +60,28 @@ html:not(.rl-left-panel-disabled) #rl-left { } #rl-left > .resizer, -.rl-side-preview-pane #V-MailMessageList > .resizer { +.rl-side-preview-pane #V-MailMessageList .resizer { cursor: ew-resize; - height: 100%; + cursor: col-resize; right: 0; top: 0; + bottom: 0; width: 5px; } - -.rl-side-preview-pane #V-MailMessageList { - resize: horizontal; /*overflow: auto;*/ - min-width: 320px; - max-width: 60%; -} -.rl-bottom-preview-pane #V-MailMessageList { - resize: vertical; /*overflow: auto;*/ - min-height: 200px; - max-height: 60%; -} -.rl-bottom-preview-pane #V-MailMessageList > .resizer { +.rl-bottom-preview-pane #V-MailMessageList .resizer { cursor: ns-resize; - height: 5px; - left: 0; + cursor: row-resize; bottom: 0; - width: 100%; + left: 0; + right: 0; + height: 5px; } html:not(.rl-left-panel-disabled) #rl-left > .resizer, -#V-MailMessageList > .resizer { +#V-MailMessageList .resizer { display: block; } -#V-MailMessageList { - position: relative; - height: 100%; - width: 50%; -} - -#V-MailMessageView { - position: absolute; - top: 0; - bottom: 13px; - right: 0; - left: 0; - - .b-message-view-backdrop { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, .4); - z-index: 10; - text-align: center; - - .backdrop-message { - position: relative; - display: inline-block; - color: white; - font-size: 20px; - line-height: 30px; - background: rgba(0, 0, 0, .6); - padding: 15px; - border-radius: var(--border-radius, 5px); - text-shadow: 0 1px 1px #000; - transform: translateY(-50%); - top: 50%; - } - } -} - -html.rl-side-preview-pane { - #V-MailMessageView { - left: 50%; - } -} - #top-system-dropdown-id::after, #button-add-prop-dropdown-id::after { content: '▼'; @@ -201,16 +149,6 @@ html.rl-side-preview-pane { #rl-left { width: @rlLeftWidth + 20; } - - #V-MailMessageList { - width: 40%; - } - - html.rl-side-preview-pane { - #V-MailMessageView { - left: 40%; - } - } } /* mobile and tablet */ @@ -253,17 +191,6 @@ html.rl-left-panel-disabled { } } -html.rl-no-preview-pane { - - #V-MailMessageList { - position: absolute; - left: 0; - right: 0; - width: auto; - } - -} - html.rl-mobile #rl-left > .resizer, html.rl-no-preview-pane #rl-right .resizer { display: none !important; @@ -285,21 +212,6 @@ html.rl-mobile { } } -html.rl-bottom-preview-pane { - - #V-MailMessageList { - height: 300px; - position: absolute; - left: 0; - right: 0; - width: auto; - } - - #V-MailMessageView { - top: 300px; - } -} - html.rl-mobile .hide-mobile, html:not(.rl-mobile) .show-mobile { display: none !important; diff --git a/dev/Styles/User/MessageList.less b/dev/Styles/User/MessageList.less index 3b7d9363e..0772e3ed7 100644 --- a/dev/Styles/User/MessageList.less +++ b/dev/Styles/User/MessageList.less @@ -5,6 +5,29 @@ html.rl-mobile, html.rl-no-preview-pane { } } +#V-MailMessageList { + position: relative; +} +.rl-side-preview-pane #V-MailMessageList { + height: 100%; +} +.rl-no-preview-pane #V-MailMessageList { + height: 100%; + width: 100%; +} +.rl-side-preview-pane #V-MailMessageList .messageList { + max-width: 50vw; + min-width: 320px; + overflow: auto; + resize: horizontal; +} +.rl-bottom-preview-pane #V-MailMessageList .messageList { + max-height: 50vh; + min-height: 200px; + overflow: auto; + resize: vertical; +} + #V-MailMessageList.focused .messageList { border-color: #9d9d9d; box-shadow: var(--smMainShadow); diff --git a/dev/Styles/User/MessageView.less b/dev/Styles/User/MessageView.less index b7e27b2c7..6cc702c24 100644 --- a/dev/Styles/User/MessageView.less +++ b/dev/Styles/User/MessageView.less @@ -1,4 +1,35 @@ +#V-MailMessageView { + flex-grow: 1; + overflow: auto; + padding-bottom: @rlLowMargin; + + .b-message-view-backdrop { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, .4); + z-index: 10; + text-align: center; + + .backdrop-message { + position: relative; + display: inline-block; + color: white; + font-size: 20px; + line-height: 30px; + background: rgba(0, 0, 0, .6); + padding: 15px; + border-radius: var(--border-radius, 5px); + text-shadow: 0 1px 1px #000; + transform: translateY(-50%); + top: 50%; + } + } +} + html.rl-no-preview-pane { #rl-right:not(.message-selected) #V-MailMessageView { display: none; diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js index fd89f66a5..b2af392ea 100644 --- a/dev/View/User/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -48,6 +48,9 @@ import { ComposePopupView } from 'View/Popup/Compose'; import { MessageModel } from 'Model/Message'; +import { Layout, ClientSideKeyNameMessageListSize } from 'Common/EnumsUser'; +import { setLayoutResizer } from 'Common/UtilsUser'; + const canBeMovedHelper = () => MessagelistUserStore.hasCheckedOrSelected(), @@ -564,6 +567,23 @@ export class MailMessageList extends AbstractViewRight { const b_content = dom.querySelector('.b-content'), eqs = (ev, s) => ev.target.closestWithin(s, dom); + setTimeout(() => { + // initMailboxLayoutResizer + const top = dom.querySelector('.messageList'), + fToggle = () => { + let layout = SettingsUserStore.layout(); + setLayoutResizer(top, ClientSideKeyNameMessageListSize, + (ThemeStore.isMobile() || Layout.NoPreview === layout) + ? 0 + : (Layout.SidePreview === layout ? 'Width' : 'Height') + ); + }; + if (top) { + fToggle(); + addEventListener('rl-layout', fToggle); + } + }, 1); + this.selector.init(b_content, Scope.MessageList); addEventsListeners(dom, { From 58e1c2e5350f80f42b7d6797d03096b4df922934 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 9 Feb 2023 09:02:30 +0100 Subject: [PATCH 03/63] Bugfix: default preview-pane layout width/height --- dev/Styles/User/MessageList.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev/Styles/User/MessageList.less b/dev/Styles/User/MessageList.less index 0772e3ed7..e5ae5b3e8 100644 --- a/dev/Styles/User/MessageList.less +++ b/dev/Styles/User/MessageList.less @@ -20,8 +20,10 @@ html.rl-mobile, html.rl-no-preview-pane { min-width: 320px; overflow: auto; resize: horizontal; + width: 35vw; } .rl-bottom-preview-pane #V-MailMessageList .messageList { + height: 35vh; max-height: 50vh; min-height: 200px; overflow: auto; From 4145c87e55e52bd32a60b69aaf75f9cf1e52f035 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 9 Feb 2023 09:29:19 +0100 Subject: [PATCH 04/63] Resolve #931 --- .../libraries/RainLoop/Actions/Contacts.php | 66 +++-------- .../RainLoop/Providers/AddressBook.php | 13 --- .../RainLoop/Providers/AddressBook/Utils.php | 105 +++++++++++------- 3 files changed, 82 insertions(+), 102 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php index 4da915d6f..002a8f384 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php @@ -133,9 +133,8 @@ trait Contacts \ini_set('auto_detect_line_endings', '1'); $mData = $this->FilesProvider()->GetFile($oAccount, $sSavedName); if ($mData) { - $sFileStart = \fread($mData, 20); + $sFileStart = \fread($mData, 128); \rewind($mData); - if (false !== $sFileStart) { $sFileStart = \trim($sFileStart); if (false !== \strpos($sFileStart, 'BEGIN:VCARD')) { @@ -254,65 +253,30 @@ trait Contacts private function importContactsFromVcfFile(\RainLoop\Model\Account $oAccount, /*resource*/ $rFile): int { - $iCount = 0; - if ($oAccount && \is_resource($rFile)) { - $oAddressBookProvider = $this->AddressBookProvider($oAccount); - if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) { - $sFile = \stream_get_contents($rFile); - if (\is_resource($rFile)) { - \fclose($rFile); - } - - if (is_string($sFile) && 5 < \strlen($sFile)) { - $this->Logger()->Write('Import contacts from vcf'); - $iCount = $oAddressBookProvider->ImportVcfFile($sFile); - } + $oAddressBookProvider = $this->AddressBookProvider($oAccount); + if (\is_resource($rFile) && $oAddressBookProvider && $oAddressBookProvider->IsActive()) { + $sFile = \stream_get_contents($rFile); + if (\is_string($sFile) && 5 < \strlen($sFile)) { + $this->Logger()->Write('Import contacts from vcf'); + return $oAddressBookProvider->ImportVcfFile($sFile); } } - - return $iCount; + return 0; } private function importContactsFromCsvFile(\RainLoop\Model\Account $oAccount, /*resource*/ $rFile, string $sFileStart): int { $iCount = 0; - $aHeaders = null; - $aData = array(); - - if ($oAccount && \is_resource($rFile)) { - $oAddressBookProvider = $this->AddressBookProvider($oAccount); - if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) { - $sDelimiter = ((int)\strpos($sFileStart, ',') > (int)\strpos($sFileStart, ';')) ? ',' : ';'; - - \setlocale(LC_CTYPE, 'en_US.UTF-8'); - while (false !== ($mRow = \fgetcsv($rFile, 5000, $sDelimiter, '"'))) { - if (null === $aHeaders) { - if (3 >= \count($mRow)) { - return 0; - } - - $aHeaders = $mRow; - - foreach ($aHeaders as $iIndex => $sHeaderValue) { - $aHeaders[$iIndex] = \MailSo\Base\Utils::Utf8Clear($sHeaderValue); - } - } else { - $aNewItem = array(); - foreach ($aHeaders as $iIndex => $sHeaderValue) { - $aNewItem[$sHeaderValue] = isset($mRow[$iIndex]) ? $mRow[$iIndex] : ''; - } - - $aData[] = $aNewItem; - } - } - - if (\count($aData)) { - $this->oLogger->Write('Import contacts from csv'); - $iCount = $oAddressBookProvider->ImportCsvArray($aData); + $oAddressBookProvider = $this->AddressBookProvider($oAccount); + if (\is_resource($rFile) && $oAddressBookProvider && $oAddressBookProvider->IsActive()) { + $this->oLogger->Write('Import contacts from csv'); + $sDelimiter = ((int)\strpos($sFileStart, ',') > (int)\strpos($sFileStart, ';')) ? ',' : ';'; + foreach (\RainLoop\Providers\AddressBook\Utils::CsvStreamToContacts($rFile, $sDelimiter) as $oContact) { + if ($oAddressBookProvider->ContactSave($oContact)) { + ++$iCount; } } } - return $iCount; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php index 6136e07c5..6e2125724 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php @@ -83,19 +83,6 @@ class AddressBook extends AbstractProvider return $this->IsActive() ? $this->oDriver->IncFrec($aEmails, $bCreateAuto) : false; } - public function ImportCsvArray(array $aCsvData) : int - { - $iCount = 0; - if ($this->IsActive()) { - foreach (AddressBook\Utils::CsvArrayToContacts($aCsvData) as $oContact) { - if ($this->ContactSave($oContact)) { - ++$iCount; - } - } - } - return $iCount; - } - public function ImportVcfFile(string $sVcfData) : int { $iCount = 0; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php index 00834404d..62b1250f9 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php @@ -22,20 +22,29 @@ class Utils 'namesuffix' => 4, 'shortname' => 'NICKNAME', 'nickname' => 'NICKNAME', + 'birthday' => 'BDAY', + + 'mobile' => array('TEL', 'CELL'), + 'mobilephone' => array('TEL', 'CELL'), + + 'businessemail' => array('EMAIL', 'WORK'), + 'businessemail2' => array('EMAIL', 'WORK'), + 'businessemail3' => array('EMAIL', 'WORK'), 'businessphone' => array('TEL', 'WORK'), 'businessphone2' => array('TEL', 'WORK'), 'businessphone3' => array('TEL', 'WORK'), + 'businessmobile' => array('TEL', 'WORK,CELL'), + 'businessmobilephone' => array('TEL', 'WORK,CELL'), + 'businessweb' => array('URL', 'WORK'), + 'businesswebpage' => array('URL', 'WORK'), + 'businesswebsite' => array('URL', 'WORK'), 'companyphone' => array('TEL', 'WORK'), 'companymainphone' => array('TEL', 'WORK'), + + 'primaryphone' => array('TEL', 'PREF,HOME'), 'homephone' => array('TEL', 'HOME'), 'homephone2' => array('TEL', 'HOME'), 'homephone3' => array('TEL', 'HOME'), - 'mobile' => array('TEL', 'CELL'), - 'mobilephone' => array('TEL', 'CELL'), - 'businessmobile' => array('TEL', 'WORK,CELL'), - 'businessmobilephone' => array('TEL', 'WORK,CELL'), - 'otherphone' => 'TEL', - 'primaryphone' => array('TEL', 'PREF,HOME'), 'email' => array('EMAIL', 'HOME'), 'email2' => array('EMAIL', 'HOME'), 'email3' => array('EMAIL', 'HOME'), @@ -48,53 +57,73 @@ class Utils 'emailaddress' => array('EMAIL', 'HOME'), 'email2address' => array('EMAIL', 'HOME'), 'email3address' => array('EMAIL', 'HOME'), - 'otheremail' => 'EMAIL', - 'businessemail' => array('EMAIL', 'WORK'), - 'businessemail2' => array('EMAIL', 'WORK'), - 'businessemail3' => array('EMAIL', 'WORK'), 'personalemail' => array('EMAIL', 'HOME'), 'personalemail2' => array('EMAIL', 'HOME'), 'personalemail3' => array('EMAIL', 'HOME'), + 'personalwebsite' => array('URL', 'HOME'), + + 'otheremail' => 'EMAIL', + 'otherphone' => 'TEL', 'notes' => 'NOTE', 'web' => 'URL', - 'businessweb' => array('URL', 'WORK'), 'webpage' => 'URL', - 'businesswebpage' => array('URL', 'WORK'), - 'website' => 'URL', - 'businesswebsite' => array('URL', 'WORK'), - 'personalwebsite' => 'URL', - 'birthday' => 'BDAY' + 'website' => 'URL' +/* + TODO: + 'company' => '', + 'department' => '', + 'jobtitle' => '', + 'officelocation' => '', + 'homestreet' => '', + 'homecity' => '', + 'homestate' => '', + 'homepostalcode' => '', + 'homecountry' => '', + 'businessstreet' => '', + 'businesscity' => '', + 'businessstate' => '', + 'businesspostalcode' => '', + 'businesscountry' => '', +*/ ); - public static function CsvArrayToContacts(array $aCsvData) : iterable + public static function CsvStreamToContacts(/*resource*/ $rFile, string $sDelimiter) : iterable { - foreach ($aCsvData as $aItem) { + \setlocale(LC_CTYPE, 'en_US.UTF-8'); + + $aHeaders = \fgetcsv($rFile, 5000, $sDelimiter, '"'); + if (!$aHeaders || 3 >= \count($aHeaders)) { + return; + } + foreach ($aHeaders as $iIndex => $sItemName) { + $sItemName = \MailSo\Base\Utils::Utf8Clear($sItemName); + $sItemName = \strtoupper(\trim(\preg_replace('/[\s\-]+/', '', $sItemName))); + if (!\array_key_exists($sItemName, \Sabre\VObject\Component\VCard::$propertyMap)) { + $sItemName = \strtolower($sItemName); + $sItemName = isset(static::$aMap[$sItemName]) ? static::$aMap[$sItemName] : null; + } + $aHeaders[$iIndex] = $sItemName; + } + + while (false !== ($mRow = \fgetcsv($rFile, 5000, $sDelimiter, '"'))) { \MailSo\Base\Utils::ResetTimeLimit(); $iCount = 0; $oVCard = new \Sabre\VObject\Component\VCard; $aName = ['','','','','']; - foreach ($aItem as $sItemName => $sItemValue) { - $sItemName = \strtoupper(\trim(\preg_replace('/[\s\-]+/', '', $sItemName))); + foreach ($mRow as $iIndex => $sItemValue) { + $sItemName = $aHeaders[$iIndex]; $sItemValue = \trim($sItemValue); - if (!empty($sItemName) && !empty($sItemValue)) { - if (\array_key_exists($sItemName, \Sabre\VObject\Component\VCard::$propertyMap)) { - $mData = $sItemName; + if (isset($sItemName) && !empty($sItemValue)) { + $mType = \is_array($sItemName) ? $sItemName[0] : $sItemName; + ++$iCount; + if (\is_int($mType)) { + $aName[$mType] = $sItemValue; + } else if (\is_array($sItemName)) { + $oVCard->add($mType, $sItemValue, ['type' => $sItemName[1]]); + } else if ('FN' === $mType || 'NICKNAME' === $mType) { + $oVCard->$mType = $sItemValue; } else { - $sItemName = \strtolower($sItemName); - $mData = !empty($sItemName) && isset($aMap[$sItemName]) ? $aMap[$sItemName] : null; - } - if ($mData) { - $mType = \is_array($mData) ? $mData[0] : $mData; - ++$iCount; - if (\is_int($mType)) { - $aName[$mType] = $sItemValue; - } else if (\is_array($mData)) { - $oVCard->add($mType, $sItemValue, ['type' => $mData[1]]); - } else if ('FN' === $mType || 'NICKNAME' === $mType) { - $oVCard->$mType = $sItemValue; - } else { - $oVCard->add($mType, $sItemValue); - } + $oVCard->add($mType, $sItemValue); } } } From e6bdb37ca1af0ec944d01be859e0833afea846f9 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 9 Feb 2023 09:29:45 +0100 Subject: [PATCH 05/63] Kolab error: $id is string, should be int --- plugins/kolab/KolabAddressBook.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kolab/KolabAddressBook.php b/plugins/kolab/KolabAddressBook.php index bd0bbba39..83b225ed5 100644 --- a/plugins/kolab/KolabAddressBook.php +++ b/plugins/kolab/KolabAddressBook.php @@ -156,7 +156,7 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt return false; } - $id = $oContact->id; + $id = \intval($oContact->id); $sUID = ''; $oVCard = $oContact->vCard; From 9466fc71a81bb403bdb1eca315657db0dd092e8d Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 9 Feb 2023 10:12:13 +0100 Subject: [PATCH 06/63] Reduce memory usage on addressbook vcf import --- .../libraries/RainLoop/Actions/Contacts.php | 34 ++++++++++++++----- .../RainLoop/Providers/AddressBook.php | 20 ----------- .../RainLoop/Providers/AddressBook/Utils.php | 8 ++--- 3 files changed, 27 insertions(+), 35 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php index 002a8f384..2805556df 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php @@ -253,15 +253,24 @@ trait Contacts private function importContactsFromVcfFile(\RainLoop\Model\Account $oAccount, /*resource*/ $rFile): int { + $iCount = 0; $oAddressBookProvider = $this->AddressBookProvider($oAccount); if (\is_resource($rFile) && $oAddressBookProvider && $oAddressBookProvider->IsActive()) { - $sFile = \stream_get_contents($rFile); - if (\is_string($sFile) && 5 < \strlen($sFile)) { + try + { $this->Logger()->Write('Import contacts from vcf'); - return $oAddressBookProvider->ImportVcfFile($sFile); + foreach (\RainLoop\Providers\AddressBook\Utils::VcfStreamToContacts($rFile) as $oContact) { + if ($oAddressBookProvider->ContactSave($oContact)) { + ++$iCount; + } + } + } + catch (\Throwable $oExc) + { + $this->Logger()->WriteException($oExc); } } - return 0; + return $iCount; } private function importContactsFromCsvFile(\RainLoop\Model\Account $oAccount, /*resource*/ $rFile, string $sFileStart): int @@ -269,13 +278,20 @@ trait Contacts $iCount = 0; $oAddressBookProvider = $this->AddressBookProvider($oAccount); if (\is_resource($rFile) && $oAddressBookProvider && $oAddressBookProvider->IsActive()) { - $this->oLogger->Write('Import contacts from csv'); - $sDelimiter = ((int)\strpos($sFileStart, ',') > (int)\strpos($sFileStart, ';')) ? ',' : ';'; - foreach (\RainLoop\Providers\AddressBook\Utils::CsvStreamToContacts($rFile, $sDelimiter) as $oContact) { - if ($oAddressBookProvider->ContactSave($oContact)) { - ++$iCount; + try + { + $this->Logger()->Write('Import contacts from csv'); + $sDelimiter = ((int)\strpos($sFileStart, ',') > (int)\strpos($sFileStart, ';')) ? ',' : ';'; + foreach (\RainLoop\Providers\AddressBook\Utils::CsvStreamToContacts($rFile, $sDelimiter) as $oContact) { + if ($oAddressBookProvider->ContactSave($oContact)) { + ++$iCount; + } } } + catch (\Throwable $oExc) + { + $this->Logger()->WriteException($oExc); + } } return $iCount; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php index 6e2125724..affaecd99 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php @@ -82,24 +82,4 @@ class AddressBook extends AbstractProvider { return $this->IsActive() ? $this->oDriver->IncFrec($aEmails, $bCreateAuto) : false; } - - public function ImportVcfFile(string $sVcfData) : int - { - $iCount = 0; - if ($this->IsActive()) { - try - { - foreach (AddressBook\Utils::VcfFileToContacts($sVcfData) as $oContact) { - if ($this->ContactSave($oContact)) { - ++$iCount; - } - } - } - catch (\Throwable $oExc) - { - $this->Logger()->WriteException($oExc); - } - } - return $iCount; - } } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php index 62b1250f9..709a729bb 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php @@ -198,13 +198,9 @@ class Utils )); } - public static function VcfFileToContacts(string $sVcfData) : iterable + public static function VcfStreamToContacts(/*resource*/ $rFile) : iterable { - $sVcfData = \trim($sVcfData); - if ("\xef\xbb\xbf" === \substr($sVcfData, 0, 3)) { - $sVcfData = \substr($sVcfData, 3); - } - $oVCardSplitter = new \Sabre\VObject\Splitter\VCard($sVcfData); + $oVCardSplitter = new \Sabre\VObject\Splitter\VCard($rFile); if ($oVCardSplitter) { while ($oVCard = $oVCardSplitter->getNext()) { if ($oVCard instanceof \Sabre\VObject\Component\VCard) { From 8d6eb96c8fa030dbe2fb24ea9cb89faaca7e08c0 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 9 Feb 2023 10:37:43 +0100 Subject: [PATCH 07/63] v2.25.5 --- .../files/usr/local/include/application.ini | 2 +- CHANGELOG.md | 39 +++++++++++++++++++ README.md | 18 ++++----- integrations/cloudron/DESCRIPTION.md | 2 +- integrations/cloudron/Dockerfile | 2 +- .../nextcloud/snappymail/appinfo/info.xml | 2 +- integrations/virtualmin/snappymail.pl | 2 +- package.json | 2 +- plugins/login-override/index.php | 4 +- 9 files changed, 56 insertions(+), 17 deletions(-) diff --git a/.docker/release/files/usr/local/include/application.ini b/.docker/release/files/usr/local/include/application.ini index c554d5a28..f28bfa436 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.4" +current = "2.25.5" saved = "Sun, 18 Dec 2022 22:10:48 +0000" diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b3a2da9a..96dc74288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +## 2.25.5 – 2023-02-09 + +## Added +- New dark themes by @TheCuteFoxxy + [#925](https://github.com/the-djmaze/snappymail/pull/925) +- External images option 'Always when DKIM is valid' for + [#201](https://github.com/the-djmaze/snappymail/pull/201) +- Image whitelist menu on message for + [#201](https://github.com/the-djmaze/snappymail/pull/201) + +## Changed +- pt-PT translation by @ner00 + [#917](https://github.com/the-djmaze/snappymail/pull/917) and + [#920](https://github.com/the-djmaze/snappymail/pull/920) +- fr-FR translation by @hguilbert + [#919](https://github.com/the-djmaze/snappymail/pull/919) +- Layout section rl-right now is `display: flex` and solves + [#928](https://github.com/the-djmaze/snappymail/pull/928) +- Some Themes background to `cover` + [#918](https://github.com/the-djmaze/snappymail/pull/918) +- Speedup cleanHtml() parser +- Reduce memory usage on addressbook import + +## Fixed +- Prevent loading loop between MessageList and FolderInformation requests +- Admin -> Config layout +- Inline images sometimes failed +- Undefined index: cid + [#921](https://github.com/the-djmaze/snappymail/pull/921) +- On upgrade prevent Apache access errors for + [#358](https://github.com/the-djmaze/snappymail/pull/358) +- Import contacts as CSV is broken + [#931](https://github.com/the-djmaze/snappymail/pull/931) + +### Removed +- Blockquote height calculator for + [#902](https://github.com/the-djmaze/snappymail/pull/902) + + ## 2.25.4 – 2023-02-06 ## Added diff --git a/README.md b/README.md index fc5751bfe..4e7d63ca6 100644 --- a/README.md +++ b/README.md @@ -142,24 +142,24 @@ RainLoop 1.17 vs SnappyMail |js/* |RainLoop |Snappy | |--------------- |--------: |--------: | |admin.js |2.170.153 | 81.589 | -|app.js |4.207.787 | 415.987 | +|app.js |4.207.787 | 416.123 | |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.561 | +|TOTAL |8.240.095 | 778.697 | |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.740 |139.456 | 64.135 |110.485 | 54.920 | +|app.min.js | 515.367 | 191.764 |139.456 | 64.128 |110.485 | 54.852 | |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.774 |358.761 | 98.968 |299.485 | 86.070 | -|TOTAL user+sieve |1.217.635 | 326.340 |358.761 |109.395 |299.485 | 95.483 | +|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 | 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 | 82.190 | 46.946 | 17.063 | 14.669 | -|app.min.css | 274.947 | 66.363 | 39.647 | 15.092 | 13.246 | +|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 | |boot.css | | 1.326 | | 664 | 545 | |boot.min.css | | 1.071 | | 590 | 474 | -|admin.css | | 30.535 | | 6.980 | 6.091 | -|admin.min.css | | 24.733 | | 6.336 | 5.587 | +|admin.css | | 30.540 | | 6.984 | 6.093 | +|admin.min.css | | 24.738 | | 6.340 | 5.589 | ### PGP RainLoop uses the old OpenPGP.js v2 diff --git a/integrations/cloudron/DESCRIPTION.md b/integrations/cloudron/DESCRIPTION.md index 00c49049c..c656b295f 100644 --- a/integrations/cloudron/DESCRIPTION.md +++ b/integrations/cloudron/DESCRIPTION.md @@ -1,4 +1,4 @@ -This app packages SnappyMail 2.25.4. +This app packages SnappyMail 2.25.5. SnappyMail is a simple, modern, lightweight & fast web-based email client. diff --git a/integrations/cloudron/Dockerfile b/integrations/cloudron/Dockerfile index cf81c0aaa..41bb1de48 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.4 +VERSION=2.25.5 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 9a18b5360..fcaaad583 100644 --- a/integrations/nextcloud/snappymail/appinfo/info.xml +++ b/integrations/nextcloud/snappymail/appinfo/info.xml @@ -3,7 +3,7 @@ snappymail SnappyMail SnappyMail Webmail - 2.25.4 + 2.25.5 agpl SnappyMail, RainLoop Team, Nextgen-Networks, Tab Fitts, Nathan Kinkade, Pierre-Alain Bandinelli Date: Thu, 9 Feb 2023 12:54:36 +0100 Subject: [PATCH 08/63] Get account local sqlite AddressBook working (but still inactive) --- plugins/kolab/KolabAddressBook.php | 16 ++++--- .../Providers/AddressBook/PdoAddressBook.php | 42 ++++++++++++------- .../RainLoop/Providers/AddressBook/Utils.php | 12 +++--- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/plugins/kolab/KolabAddressBook.php b/plugins/kolab/KolabAddressBook.php index 83b225ed5..8c7df9316 100644 --- a/plugins/kolab/KolabAddressBook.php +++ b/plugins/kolab/KolabAddressBook.php @@ -133,12 +133,16 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt $oParams->iLimit = 999; // Is the max $oMessageList = $this->MailClient()->MessageList($oParams); foreach ($oMessageList as $oMessage) { - if ($rCsv) { - $oContact = $this->MessageAsContact($oMessage); - \RainLoop\Providers\AddressBook\Utils::VCardToCsv($rCsv, $oContact, $bCsvHeader); - $bCsvHeader = false; - } else if ($xCard = $this->fetchXCardFromMessage($oMessage)) { - echo $xCard->serialize(); + try { + if ($rCsv) { + $oContact = $this->MessageAsContact($oMessage); + \RainLoop\Providers\AddressBook\Utils::VCardToCsv($rCsv, $oContact->vCard, $bCsvHeader); + $bCsvHeader = false; + } else if ($xCard = $this->fetchXCardFromMessage($oMessage)) { + echo $xCard->serialize(); + } + } catch (\Throwable $oExc) { + $this->oLogger && $this->oLogger->WriteException($oExc); } } } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php index 74401b81c..e57dd5894 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php @@ -52,11 +52,17 @@ class PdoAddressBook $sDsn = 'sqlite:' . APP_PRIVATE_DATA . 'AddressBook.sqlite'; /* // TODO: use local db? - $homedir = \RainLoop\Api::Actions()->StorageProvider()->GenerateFilePath( - $oAccount, - \RainLoop\Providers\Storage\Enumerations\StorageType::ROOT - ); - $sDsn = 'sqlite:' . $homedir . '/AddressBook.sqlite'; + $oAccount = \RainLoop\Api::Actions()->getMainAccountFromToken(false); + if ($oAccount) { + $homedir = \RainLoop\Api::Actions()->StorageProvider()->GenerateFilePath( + $oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::ROOT + ); + if (!\is_file($homedir . 'AddressBook.sqlite') && \is_file(APP_PRIVATE_DATA . '/AddressBook.sqlite')) { + \copy(APP_PRIVATE_DATA . '/AddressBook.sqlite', $homedir . 'AddressBook.sqlite'); + } + $sDsn = 'sqlite:' . $homedir . 'AddressBook.sqlite'; + } */ } else { $sDsn = \trim($oConfig->Get('contacts', 'pdo_dsn', '')); @@ -308,6 +314,7 @@ class PdoAddressBook public function Export(string $sType = 'vcf') : bool { if (1 > $this->iUserID) { + \SnappyMail\Log::warning('PdoAddressBook', 'Export() invalid $iUserID'); return false; } @@ -317,16 +324,21 @@ class PdoAddressBook $aDatabaseSyncData = $this->prepareDatabaseSyncData(); if (\count($aDatabaseSyncData)) { foreach ($aDatabaseSyncData as $mData) { - if ($mData && isset($mData['id_contact'], $mData['deleted']) && !$mData['deleted']) { - $oContact = $this->GetContactByID($mData['id_contact']); - if ($oContact) { - if ($rCsv) { - Utils::VCardToCsv($rCsv, $oContact, $bCsvHeader); - $bCsvHeader = false; - } else { - echo $oContact->vCard->serialize(); + try { +// if ($mData && isset($mData['id_contact'], $mData['deleted']) && !$mData['deleted']) { + if ($mData && !empty($mData['id_contact'])) { + $oContact = $this->GetContactByID($mData['id_contact']); + if ($oContact) { + if ($rCsv) { + Utils::VCardToCsv($rCsv, $oContact->vCard, $bCsvHeader); + $bCsvHeader = false; + } else { + echo $oContact->vCard->serialize(); + } } } + } catch (\Throwable $oExc) { + $this->oLogger && $this->oLogger->WriteException($oExc); } } } @@ -337,6 +349,7 @@ class PdoAddressBook public function ContactSave(Contact $oContact) : bool { if (1 > $this->iUserID) { + \SnappyMail\Log::warning('PdoAddressBook', 'ContactSave() invalid $iUserID'); return false; } @@ -436,6 +449,7 @@ class PdoAddressBook public function DeleteContacts(array $aContactIds) : bool { if (1 > $this->iUserID) { + \SnappyMail\Log::warning('PdoAddressBook', 'DeleteContacts() invalid $iUserID'); return false; } @@ -744,7 +758,7 @@ class PdoAddressBook p.prop_value as jcard FROM rainloop_ab_contacts AS c LEFT JOIN rainloop_ab_properties AS p ON (p.id_contact = c.id_contact AND p.prop_type = :prop_type) - WHERE c.deleted = 0 AND c.id_user = :id_user'; + WHERE c.id_user = :id_user AND c.deleted = 0'; $aParams = array( ':id_user' => array($this->iUserID, \PDO::PARAM_INT), diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php index 709a729bb..983e67aa7 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Utils.php @@ -2,6 +2,8 @@ namespace RainLoop\Providers\AddressBook; +use Sabre\VObject\Component\VCard; + class Utils { private static $aMap = array( @@ -98,7 +100,7 @@ class Utils foreach ($aHeaders as $iIndex => $sItemName) { $sItemName = \MailSo\Base\Utils::Utf8Clear($sItemName); $sItemName = \strtoupper(\trim(\preg_replace('/[\s\-]+/', '', $sItemName))); - if (!\array_key_exists($sItemName, \Sabre\VObject\Component\VCard::$propertyMap)) { + if (!\array_key_exists($sItemName, VCard::$propertyMap)) { $sItemName = \strtolower($sItemName); $sItemName = isset(static::$aMap[$sItemName]) ? static::$aMap[$sItemName] : null; } @@ -108,7 +110,7 @@ class Utils while (false !== ($mRow = \fgetcsv($rFile, 5000, $sDelimiter, '"'))) { \MailSo\Base\Utils::ResetTimeLimit(); $iCount = 0; - $oVCard = new \Sabre\VObject\Component\VCard; + $oVCard = new VCard; $aName = ['','','','','']; foreach ($mRow as $iIndex => $sItemValue) { $sItemName = $aHeaders[$iIndex]; @@ -138,7 +140,7 @@ class Utils } } - public static function VCardToCsv($stream, Classes\Contact $oContact, bool $bWithHeader = false)/* : int|false*/ + public static function VCardToCsv($stream, VCard $oVCard, bool $bWithHeader = false)/* : int|false*/ { $aData = array(); if ($bWithHeader) { @@ -153,8 +155,6 @@ class Utils )); } - $oVCard = $oContact->vCard; - $aName = isset($oVCard->N) ? $oVCard->N->getParts() : ['','','','','']; $adrHome = $oVCard->getByType('ADR', 'HOME'); @@ -203,7 +203,7 @@ class Utils $oVCardSplitter = new \Sabre\VObject\Splitter\VCard($rFile); if ($oVCardSplitter) { while ($oVCard = $oVCardSplitter->getNext()) { - if ($oVCard instanceof \Sabre\VObject\Component\VCard) { + if ($oVCard instanceof VCard) { \MailSo\Base\Utils::ResetTimeLimit(); $oContact = new Classes\Contact(); $oContact->setVCard($oVCard); From 08d4cf364800eba13d9dc47d2804bc8327c0fac3 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 9 Feb 2023 12:57:00 +0100 Subject: [PATCH 09/63] Bugfix: upgrade Undefined variable $items --- snappymail/v/0.0.0/app/libraries/snappymail/upgrade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/upgrade.php b/snappymail/v/0.0.0/app/libraries/snappymail/upgrade.php index 7ab4e605c..8586e1590 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/upgrade.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/upgrade.php @@ -236,7 +236,7 @@ abstract class Upgrade new \RecursiveDirectoryIterator("{$dir}/{$folder}", \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST ); - foreach ($items as $item) { + foreach ($iterator as $item) { if ($item->isDir()) { \chmod($item, 0755); } else if ($item->isFile()) { From 2b7962805f9169a518b4bb602f30e4112ddf10e8 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 9 Feb 2023 13:50:09 +0100 Subject: [PATCH 10/63] PdoAddressBook `ORDER BY deleted DESC` to prevent sync and export issues --- .../RainLoop/Providers/AddressBook/PdoAddressBook.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php index e57dd5894..63cfbc289 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php @@ -107,9 +107,12 @@ class PdoAddressBook private function prepareDatabaseSyncData() : array { $aResult = array(); - $oStmt = $this->prepareAndExecute('SELECT id_contact, id_contact_str, changed, deleted, etag FROM rainloop_ab_contacts WHERE id_user = :id_user', array( - ':id_user' => array($this->iUserID, \PDO::PARAM_INT) - )); + $oStmt = $this->prepareAndExecute('SELECT id_contact, id_contact_str, changed, deleted, etag + FROM rainloop_ab_contacts + WHERE id_user = :id_user + ORDER BY deleted DESC', + array(':id_user' => array($this->iUserID, \PDO::PARAM_INT)) + ); if ($oStmt) { $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); From 6c140550062b787324717a72c03c74e134ec91a4 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 9 Feb 2023 13:52:41 +0100 Subject: [PATCH 11/63] Resolve #939 --- .../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 07d236c4e..181a6405e 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 @@ -283,7 +283,7 @@
- +
  • From b2079f72636b67619e1164e1dcbf90a9dd081d75 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 9 Feb 2023 16:16:50 +0100 Subject: [PATCH 12/63] Bugfix: whitelist fails when empty or when using `:` #938 --- dev/Model/Message.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/Model/Message.js b/dev/Model/Message.js index d7d4e54bf..1ed54bc24 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -402,19 +402,19 @@ export class MessageModel extends AbstractModel { regex = SettingsUserStore.viewImagesWhitelist() .trim() .replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&') - .replace(/[\s\r\n,;:]+/g, '|') + .replace(/[\s\r\n,;]+/g, '|') .replace(/\|+/g, '|'); if (regex) { console.log('whitelist images = '+regex); regex = new RegExp(regex); if (this.from[0]?.email.match(regex)) { - regex = 0; + regex = null; } } } let hasImages = false, isValid = src => { - if (!regex || src.match(regex)) { + if (null == regex || (regex && src.match(regex))) { return true; } hasImages = true; From f1640d7565015fb2dcfd321e7b023af4b95c21c2 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Thu, 9 Feb 2023 16:52:40 +0100 Subject: [PATCH 13/63] 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 20/63] 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 399ae06a5c7de778ce12c2546821bcc41428b839 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Fri, 10 Feb 2023 11:38:41 +0100 Subject: [PATCH 21/63] 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 0c6950d49c60819c164ca418ee9b1d750edffa10 Mon Sep 17 00:00:00 2001 From: Nivesh Krishna Date: Fri, 10 Feb 2023 20:02:17 +0530 Subject: [PATCH 22/63] 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 23/63] 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 24/63] 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 25/63] 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 26/63] 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 27/63] 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 28/63] 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 29/63] 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 30/63] 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 31/63] 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 32/63] 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 33/63] #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 34/63] 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 35/63] 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 36/63] 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 37/63] 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 38/63] 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 39/63] 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 40/63] 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 41/63] 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 42/63] 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 43/63] 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 44/63] 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 45/63] 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 46/63] 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 47/63] 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 48/63] 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 49/63] 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 50/63] 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 51/63] 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 52/63] 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 53/63] 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 54/63] 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 55/63] 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 56/63] 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 57/63] 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 58/63] 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 59/63] 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 60/63] 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 61/63] 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 62/63] 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 63/63] 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();
    - +