From e5ed52b79ea375e91e12ef04165de8fda87646d1 Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Sun, 27 Nov 2022 16:05:44 +0100 Subject: [PATCH] Changes for #714 --- dev/Knoin/AbstractModel.js | 1 + plugins/avatars/avatars.js | 7 ++- plugins/avatars/index.php | 47 ++++++++++++++----- .../libraries/MailSo/Cache/Drivers/File.php | 26 ++++------ .../0.0.0/app/libraries/RainLoop/Actions.php | 13 ++--- 5 files changed, 55 insertions(+), 39 deletions(-) diff --git a/dev/Knoin/AbstractModel.js b/dev/Knoin/AbstractModel.js index f98deade1..fe5ebab81 100644 --- a/dev/Knoin/AbstractModel.js +++ b/dev/Knoin/AbstractModel.js @@ -105,6 +105,7 @@ export class AbstractModel { // fall through case 'undefined': default: + this[key] = value; // console.log((typeof this[key])+' '+(model.name)+'.'+key+' not revived'); } } catch (e) { diff --git a/plugins/avatars/avatars.js b/plugins/avatars/avatars.js index ba872301e..43a9cec42 100644 --- a/plugins/avatars/avatars.js +++ b/plugins/avatars/avatars.js @@ -1,5 +1,4 @@ (rl => { -// if (rl.settings.get('Nextcloud')) const queue = [], avatars = new Map, @@ -45,6 +44,9 @@ fn = url=>{element.src = url}; if (url) { fn(url); + } else if (msg.avatar) { + let bimi = 'pass' == msg.from[0].dkimStatus ? 1 : 0; + fn(`?Avatar/${bimi}/${msg.avatar}`); } else { queue.push([msg, fn]); runQueue(); @@ -80,6 +82,9 @@ }; if (url) { fn(url); + } else if (msg.avatar) { + let bimi = 'pass' == msg.from[0].dkimStatus ? 1 : 0; + fn(`?Avatar/${bimi}/${msg.avatar}`); } else { // let from = msg.from[0], bimi = 'pass' == from.dkimStatus ? 1 : 0; // view.viewUserPic(`?Avatar/${bimi}/${encodeURIComponent(from.email)}`); diff --git a/plugins/avatars/index.php b/plugins/avatars/index.php index fb3145bf9..8c82541e9 100644 --- a/plugins/avatars/index.php +++ b/plugins/avatars/index.php @@ -3,10 +3,10 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin { const - NAME = 'Avatar', + NAME = 'Avatars', AUTHOR = 'SnappyMail', URL = 'https://snappymail.eu/', - VERSION = '1.0', + VERSION = '1.1', RELEASE = '2022-11-23', REQUIRED = '2.22.0', CATEGORY = 'Contacts', @@ -19,6 +19,29 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin $this->addJs('avatars.js'); $this->addJsonHook('Avatar', 'DoAvatar'); $this->addPartHook('Avatar', 'ServiceAvatar'); + // TODO: https://github.com/the-djmaze/snappymail/issues/714 +// $this->addHook('filter.json-response', 'FilterJsonResponse'); + } + + public function FilterJsonResponse(string $sAction, array &$aResponseItem) + { + if ('MessageList' === $sAction && !empty($aResponseItem['Result']['@Collection'])) { + foreach ($aResponseItem['Result']['@Collection'] as $id => $message) { + $aResponseItem['Result']['@Collection'][$id]['Avatar'] = static::encryptFrom($message['From'][0]); + } + } else if ('Message' === $sAction && !empty($aResponseItem['Result']['From'])) { + $aResponseItem['Result']['Avatar'] = static::encryptFrom($aResponseItem['Result']['From'][0]); + } + } + + private static function encryptFrom($mFrom) + { + if ($mFrom instanceof \MailSo\Mime\Email) { + $mFrom = $mFrom->jsonSerialize(); + } + return \is_array($mFrom) + ? \SnappyMail\Crypt::EncryptUrlSafe($mFrom['Email']) + : null; } /** @@ -28,7 +51,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin { $bBimi = !empty($this->jsonParam('bimi')); $sEmail = $this->jsonParam('email'); - $aResult = $this->getAvatar(\urldecode($sEmail), !empty($sEmail)); + $aResult = $this->getAvatar($sEmail, !empty($bBimi)); if ($aResult) { $aResult = [ 'type' => $aResult[0], @@ -39,15 +62,17 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin } /** - * GET /?Avatar/${bimi}/${from.email} - * Not fond of this idea because email address is exposed - * Maybe use btoa(from.email) or Crypto.subtle.encrypt({name:'AES-GCM',iv:''}, token, from.email) + * GET /?Avatar/${bimi}/Encrypted(${from.email}) + * Nextcloud Mail uses insecure unencrypted 'index.php/apps/mail/api/avatars/url/local%40example.com' */ // public function ServiceAvatar(...$aParts) public function ServiceAvatar(string $sServiceName, string $sBimi, string $sEmail) { - $aResult = $this->getAvatar(\urldecode($sEmail), !empty($sEmail)); - if ($aResult) { + $sEmail = \SnappyMail\Crypt::DecryptUrlSafe($sEmail); + $oActions = \RainLoop\Api::Actions(); + $oActions->verifyCacheByKey($sEmail, true); + if ($sEmail && ($aResult = $this->getAvatar($sEmail, !empty($sBimi)))) { + $oActions->Http()->ServerUseCache($oActions->etag($sEmail), \time(), \time() + 86400); \header('Content-Type: '.$aResult[0]); echo $aResult[1]; return true; @@ -74,7 +99,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin } $oActions = \RainLoop\Api::Actions(); - $oActions->verifyCacheByKey($sEmail); +// $oActions->verifyCacheByKey($sEmail, true); $aResult = null; @@ -88,7 +113,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin \mime_content_type($aFiles[0]), \file_get_contents($aFiles[0]) ]; - $oActions->cacheByKey($sEmail); +// $oActions->Http()->ServerUseCache($oActions->etag($sEmail), \time(), \time() + 86400); return $aResult; } @@ -167,7 +192,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin } } - $oActions->cacheByKey($sEmail); +// $oActions->Http()->ServerUseCache($oActions->etag($sEmail), \time(), \time() + 86400); return $aResult; } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php b/snappymail/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php index 0c32dc0e6..ed54f97f0 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php @@ -18,30 +18,20 @@ namespace MailSo\Cache\Drivers; */ class File implements \MailSo\Cache\DriverInterface { - /** - * @var string - */ - private $sCacheFolder; + private string $sCacheFolder; - /** - * @var string - */ - private $sKeyPrefix; + private string $sKeyPrefix = ''; function __construct(string $sCacheFolder, string $sKeyPrefix = '') { - $this->sCacheFolder = $sCacheFolder; - $this->sCacheFolder = rtrim(trim($this->sCacheFolder), '\\/').'/'; - - $this->sKeyPrefix = $sKeyPrefix; - if (!empty($this->sKeyPrefix)) - { - $this->sKeyPrefix = \str_pad(\preg_replace('/[^a-zA-Z0-9_]/', '_', - rtrim(trim($this->sKeyPrefix), '\\/')), 5, '_'); + $this->sCacheFolder = \rtrim(\trim($sCacheFolder), '\\/').'/'; + if (!empty($sKeyPrefix)) { + $sKeyPrefix = \str_pad(\preg_replace('/[^a-zA-Z0-9_]/', '_', + \rtrim(\trim($sKeyPrefix), '\\/')), 5, '_'); $this->sKeyPrefix = '__/'. - \substr($this->sKeyPrefix, 0, 2).'/'.\substr($this->sKeyPrefix, 2, 2).'/'. - $this->sKeyPrefix.'/'; + \substr($sKeyPrefix, 0, 2).'/'.\substr($sKeyPrefix, 2, 2).'/'. + $sKeyPrefix.'/'; } } 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 a3593db44..2b880b930 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -1067,25 +1067,20 @@ class Actions public function cacheByKey(string $sKey, bool $bForce = false): bool { - $bResult = false; if (!empty($sKey) && ($bForce || ($this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true)))) { $iExpires = $this->oConfig->Get('cache', 'http_expires', 3600); if (0 < $iExpires) { $this->Http()->ServerUseCache($this->etag($sKey), 1382478804, \time() + $iExpires); - $bResult = true; + return true; } } - - if (!$bResult) { - $this->Http()->ServerNoCache(); - } - - return $bResult; + $this->Http()->ServerNoCache(); + return false; } public function verifyCacheByKey(string $sKey, bool $bForce = false): void { - if (!empty($sKey) && ($bForce || $this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true))) { + if (!empty($sKey) && ($bForce || ($this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true)))) { $sIfNoneMatch = $this->Http()->GetHeader('If-None-Match', ''); if ($this->etag($sKey) === $sIfNoneMatch) { \MailSo\Base\Http::StatusHeader(304);