diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index e1557d268..78a46f355 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -864,7 +864,7 @@ export class ComposePopupView extends AbstractViewPopup { this.prepareMessageAttachments(oLastMessage, options.mode); this.aDraftInfo = ['reply', oLastMessage.uid, oLastMessage.folder]; this.sInReplyTo = oLastMessage.messageId; - this.sReferences = (oLastMessage.messageId + ' ' + oLastMessage.references).trim(); + this.sReferences = (oLastMessage.references + ' ' + oLastMessage.messageId).trim(); // OpenPGP “Transferable Public Key” // oLastMessage.autocrypt?.keydata break; @@ -875,7 +875,7 @@ export class ComposePopupView extends AbstractViewPopup { this.prepareMessageAttachments(oLastMessage, options.mode); this.aDraftInfo = ['forward', oLastMessage.uid, oLastMessage.folder]; this.sInReplyTo = oLastMessage.messageId; - this.sReferences = (oLastMessage.messageId + ' ' + oLastMessage.references).trim(); + this.sReferences = (oLastMessage.references + ' ' + oLastMessage.messageId).trim(); break; case ComposeType.Draft: diff --git a/plugins/video-on-login-screen/LICENSE b/plugins/video-on-login-screen/LICENSE new file mode 100755 index 000000000..44b915a0c --- /dev/null +++ b/plugins/video-on-login-screen/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2023 SnappyMail Team + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/video-on-login-screen/README b/plugins/video-on-login-screen/README new file mode 100755 index 000000000..ec8c5bbdb --- /dev/null +++ b/plugins/video-on-login-screen/README @@ -0,0 +1 @@ +Fullscreen background video on login screen. \ No newline at end of file diff --git a/plugins/video-on-login-screen/VERSION b/plugins/video-on-login-screen/VERSION new file mode 100755 index 000000000..b123147e2 --- /dev/null +++ b/plugins/video-on-login-screen/VERSION @@ -0,0 +1 @@ +1.1 \ No newline at end of file diff --git a/plugins/video-on-login-screen/index.php b/plugins/video-on-login-screen/index.php new file mode 100755 index 000000000..26f29b953 --- /dev/null +++ b/plugins/video-on-login-screen/index.php @@ -0,0 +1,44 @@ +addJs('js/video-on-login.js'); + $this->addHook('main.content-security-policy', 'ContentSecurityPolicy'); + } + + /** + * @return array + */ + protected function configMapping() : array + { + return array( + \RainLoop\Plugins\Property::NewInstance('mp4_file')->SetLabel('Url to a mp4 file') + ->SetPlaceholder('http://') + ->SetAllowedInJs(true) + ->SetDefaultValue(''), + \RainLoop\Plugins\Property::NewInstance('playback_rate')->SetLabel('Playback rate') + ->SetAllowedInJs(true) + ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION) + ->SetDefaultValue(array('100%', '25%', '50%', '75%', '125%', '150%', '200%')), + ); + } + + public function ContentSecurityPolicy(\SnappyMail\HTTP\CSP $CSP) + { + $vSource = $this->Config()->Get('plugin', 'mp4_file', 'self'); + $CSP->add('media-src', $vSource); + } +} diff --git a/plugins/video-on-login-screen/js/video-on-login.js b/plugins/video-on-login-screen/js/video-on-login.js new file mode 100755 index 000000000..d5d497a62 --- /dev/null +++ b/plugins/video-on-login-screen/js/video-on-login.js @@ -0,0 +1,107 @@ +(rl => { + + rl && addEventListener('rl-view-model', e => { + const id = e.detail.viewModelTemplateID; + if (e.detail && ('AdminLogin' === id || 'Login' === id)) { + let + nId = null, + script; + + let + iRate = 1, + sRate = window.rl.pluginSettingsGet('video-on-login-screen', 'playback_rate') + ; + + switch (sRate) + { + case '25%': + iRate = 0.25; + break; + case '50%': + iRate = 0.5; + break; + case '75%': + iRate = 0.75; + break; + case '125%': + iRate = 1.25; + break; + case '150%': + iRate = 1.5; + break; + case '200%': + iRate = 2; + break; + } + const + mode = 'Login' === id ? 'user' : 'admin', + + doc = document, + loginContainer = doc.querySelectorAll('#V-Login #V-AdminLogin'), + container = doc.querySelector('#rl-content'), + + ShowVideo = () => { + if (loginContainer) { + var stEl = doc.createElement('style'); + stEl.innerHTML = + ` + #video-el { + z-index: -1; + overflow: hidden; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + height: 100vh; + width: 100%; + object-fit: cover; + } + ` + var ref = doc.querySelector('script'); + ref.parentNode.insertBefore(stEl, ref); + + const oEl = doc.createElement('div'); + oEl.className = 'video-div'; + const vEl = doc.createElement('video'); + vEl.setAttribute('loop', true); + vEl.setAttribute('playsinline', ''); + vEl.setAttribute('muted', ''); + vEl.setAttribute('autoplay', ''); + vEl.muted = true; + vEl.setAttribute('playbackRate', iRate); + vEl.setAttribute('id', 'video-el'); + oEl.appendChild(vEl); + const sEl = doc.createElement('source'); + sEl.setAttribute('src', rl.pluginSettingsGet('video-on-login-screen', 'mp4_file')); + sEl.setAttribute('type', 'video/mp4'); + vEl.appendChild(sEl); + + container.before(oEl); + + } + }, + + DestroyVideo = () => { + const vEl = doc.querySelector('#video-el'); + if (vEl) { + vEl.parentElement.removeChild(vEl); + } + }; + + window.ShowVideo = ShowVideo; + + window.DestroyVideo = DestroyVideo; + + ShowVideo(); + + addEventListener(`sm-${mode}-login-response`, e => { + if (!e.detail.error) { + DestroyVideo(); + } + }); + } + }); + +})(window.rl); diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Header.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Header.php index 985e71905..65f890dbc 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Header.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Header.php @@ -156,8 +156,9 @@ public function __toString() : string } } - // https://www.rfc-editor.org/rfc/rfc2822#section-2.1.1 - return \wordwrap($this->NameWithDelimitrom() . $sResult, 78, "\r\n "); + // https://www.rfc-editor.org/rfc/rfc2822#section-2.1.1, avoid folding immediately after the header name + return $this->NameWithDelimitrom() . \wordwrap($sResult, 78 - \strlen($this->NameWithDelimitrom()) - 1, "\r\n "); + } public function IsSubject() : bool 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 old mode 100644 new mode 100755 index 146eb3b73..331d454fd --- a/snappymail/v/0.0.0/app/libraries/snappymail/http/csp.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/http/csp.php @@ -22,6 +22,7 @@ class CSP // Knockout.js requires unsafe-inline? // 'script-src' => ["'self'", "'unsafe-inline'", "'unsafe-eval'"], 'img-src' => ["'self'", 'data:'], + 'media-src' => ["'self'", 'data:'], 'style-src' => ["'self'", "'unsafe-inline'"], ];