mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Resolve #714
This commit is contained in:
parent
e5ed52b79e
commit
b89d194863
6 changed files with 106 additions and 109 deletions
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
if (messageItemHeader) {
|
||||
messageItemHeader.prepend(Element.fromHTML(
|
||||
`<img class="fromPic" data-bind="visible: viewUserPicVisible, attr: {'src': viewUserPic() }">`
|
||||
`<img class="fromPic" data-bind="visible: viewUserPicVisible, attr: {'src': viewUserPic() }" loading="lazy">`
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
|
||||
if ('MailMessageList' === e.detail.viewModelTemplateID) {
|
||||
document.getElementById('MailMessageList').content.querySelector('.messageCheckbox')
|
||||
.append(Element.fromHTML(`<img class="fromPic" data-bind="fromPic:$data">`));
|
||||
.append(Element.fromHTML(`<img class="fromPic" data-bind="fromPic:$data" loading="lazy">`));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
AUTHOR = 'SnappyMail',
|
||||
URL = 'https://snappymail.eu/',
|
||||
VERSION = '1.1',
|
||||
RELEASE = '2022-11-23',
|
||||
REQUIRED = '2.22.0',
|
||||
RELEASE = '2022-11-27',
|
||||
REQUIRED = '2.22.4',
|
||||
CATEGORY = 'Contacts',
|
||||
LICENSE = 'MIT',
|
||||
DESCRIPTION = 'Show photo of sender in message and messages list (supports BIMI and Gravatar, Contacts is still TODO)';
|
||||
|
|
@ -19,8 +19,9 @@ 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');
|
||||
// https://github.com/the-djmaze/snappymail/issues/714
|
||||
$this->Config()->Get('plugin', 'delay', true)
|
||||
|| $this->addHook('filter.json-response', 'FilterJsonResponse');
|
||||
}
|
||||
|
||||
public function FilterJsonResponse(string $sAction, array &$aResponseItem)
|
||||
|
|
@ -69,10 +70,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
public function ServiceAvatar(string $sServiceName, string $sBimi, string $sEmail)
|
||||
{
|
||||
$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;
|
||||
|
|
@ -83,6 +81,9 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
protected function configMapping() : array
|
||||
{
|
||||
return array(
|
||||
\RainLoop\Plugins\Property::NewInstance('delay')->SetLabel('Delay loading')
|
||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
|
||||
->SetDefaultValue(true),
|
||||
\RainLoop\Plugins\Property::NewInstance('bimi')->SetLabel('Use BIMI (https://bimigroup.org/)')
|
||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
|
||||
->SetDefaultValue(false),
|
||||
|
|
@ -98,28 +99,30 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
return null;
|
||||
}
|
||||
|
||||
$oActions = \RainLoop\Api::Actions();
|
||||
// $oActions->verifyCacheByKey($sEmail, true);
|
||||
$sAsciiEmail = \mb_strtolower(\MailSo\Base\Utils::IdnToAscii($sEmail, true));
|
||||
$sEmailId = \sha1($sAsciiEmail);
|
||||
|
||||
\MailSo\Base\Http::setETag($sEmailId);
|
||||
\header('Cache-Control: private');
|
||||
// \header('Expires: '.\gmdate('D, j M Y H:i:s', \time() + 86400).' UTC');
|
||||
|
||||
$aResult = null;
|
||||
|
||||
$sAsciiEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
||||
$sEmailId = \sha1(\strtolower($sAsciiEmail));
|
||||
|
||||
$sFile = \APP_PRIVATE_DATA . 'avatars/' . $sEmailId;
|
||||
$aFiles = \glob("{$sFile}.*");
|
||||
if ($aFiles) {
|
||||
\MailSo\Base\Http::setLastModified(\filemtime($aFiles[0]));
|
||||
$aResult = [
|
||||
\mime_content_type($aFiles[0]),
|
||||
\file_get_contents($aFiles[0])
|
||||
];
|
||||
// $oActions->Http()->ServerUseCache($oActions->etag($sEmail), \time(), \time() + 86400);
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
// TODO: lookup contacts vCard and return PHOTO value
|
||||
/*
|
||||
if (!$aResult) {
|
||||
$oActions = \RainLoop\Api::Actions();
|
||||
$oAccount = $oActions->getAccountFromToken();
|
||||
if ($oAccount) {
|
||||
$oAddressBookProvider = $oActions->AddressBookProvider($oAccount);
|
||||
|
|
@ -172,6 +175,7 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
$sFile . \SnappyMail\File\MimeType::toExtension($aResult[0]),
|
||||
$aResult[1]
|
||||
);
|
||||
\MailSo\Base\Http::setLastModified(\time());
|
||||
}
|
||||
|
||||
if (!$aResult) {
|
||||
|
|
@ -182,18 +186,18 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
'empty-contact' // DATA_IMAGE_USER_DOT_PIC
|
||||
];
|
||||
foreach ($aServices as $service) {
|
||||
if (\file_exists(__DIR__ . "/images/{$service}.png")) {
|
||||
$file = __DIR__ . "/images/{$service}.png";
|
||||
if (\file_exists($file)) {
|
||||
\MailSo\Base\Http::setLastModified(\filemtime($file));
|
||||
$aResult = [
|
||||
'image/png',
|
||||
\file_get_contents(__DIR__ . "/images/{$service}.png")
|
||||
\file_get_contents($file)
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// $oActions->Http()->ServerUseCache($oActions->etag($sEmail), \time(), \time() + 86400);
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ class Http
|
|||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetServer(string $sKey, $mDefault = null)
|
||||
public static function GetServer(string $sKey, $mDefault = null)
|
||||
{
|
||||
return isset($_SERVER[$sKey]) ? $_SERVER[$sKey] : $mDefault;
|
||||
}
|
||||
|
||||
public function GetMethod() : string
|
||||
{
|
||||
return $this->GetServer('REQUEST_METHOD', '');
|
||||
return static::GetServer('REQUEST_METHOD', '');
|
||||
}
|
||||
|
||||
public function IsPost() : bool
|
||||
|
|
@ -67,7 +67,7 @@ class Http
|
|||
{
|
||||
if (empty($sValueToCheck))
|
||||
{
|
||||
$sValueToCheck = $this->GetServer('REMOTE_ADDR', '');
|
||||
$sValueToCheck = static::GetServer('REMOTE_ADDR', '');
|
||||
}
|
||||
|
||||
return $this->CheckLocalhost($sValueToCheck);
|
||||
|
|
@ -84,21 +84,16 @@ class Http
|
|||
return $sRawBody;
|
||||
}
|
||||
|
||||
public function GetHeader(string $sHeader) : string
|
||||
public static function GetHeader(string $sHeader) : string
|
||||
{
|
||||
$sServerKey = 'HTTP_'.\strtoupper(\str_replace('-', '_', $sHeader));
|
||||
$sResultHeader = $this->GetServer($sServerKey, '');
|
||||
|
||||
if (0 === \strlen($sResultHeader) &&
|
||||
\MailSo\Base\Utils::FunctionCallable('apache_request_headers'))
|
||||
{
|
||||
$sResultHeader = static::GetServer($sServerKey, '');
|
||||
if (0 === \strlen($sResultHeader) && \MailSo\Base\Utils::FunctionCallable('apache_request_headers')) {
|
||||
$sHeaders = \apache_request_headers();
|
||||
if (isset($sHeaders[$sHeader]))
|
||||
{
|
||||
if (isset($sHeaders[$sHeader])) {
|
||||
$sResultHeader = $sHeaders[$sHeader];
|
||||
}
|
||||
}
|
||||
|
||||
return $sResultHeader;
|
||||
}
|
||||
|
||||
|
|
@ -109,15 +104,15 @@ class Http
|
|||
|
||||
public function IsSecure(bool $bCheckProxy = true) : bool
|
||||
{
|
||||
$sHttps = \strtolower($this->GetServer('HTTPS', ''));
|
||||
if ('on' === $sHttps || ('' === $sHttps && '443' === (string) $this->GetServer('SERVER_PORT', '')))
|
||||
$sHttps = \strtolower(static::GetServer('HTTPS', ''));
|
||||
if ('on' === $sHttps || ('' === $sHttps && '443' === (string) static::GetServer('SERVER_PORT', '')))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($bCheckProxy && (
|
||||
('https' === \strtolower($this->GetServer('HTTP_X_FORWARDED_PROTO', ''))) ||
|
||||
('on' === \strtolower($this->GetServer('HTTP_X_FORWARDED_SSL', '')))
|
||||
('https' === \strtolower(static::GetServer('HTTP_X_FORWARDED_PROTO', ''))) ||
|
||||
('on' === \strtolower(static::GetServer('HTTP_X_FORWARDED_SSL', '')))
|
||||
))
|
||||
{
|
||||
return true;
|
||||
|
|
@ -128,11 +123,11 @@ class Http
|
|||
|
||||
public function GetHost(bool $bWithRemoteUserData = false, bool $bWithoutWWW = true, bool $bWithoutPort = false) : string
|
||||
{
|
||||
$sHost = $this->GetServer('HTTP_HOST', '');
|
||||
$sHost = static::GetServer('HTTP_HOST', '');
|
||||
if (!\strlen($sHost))
|
||||
{
|
||||
$sName = $this->GetServer('SERVER_NAME');
|
||||
$iPort = (int) $this->GetServer('SERVER_PORT', 80);
|
||||
$sName = static::GetServer('SERVER_NAME');
|
||||
$iPort = (int) static::GetServer('SERVER_PORT', 80);
|
||||
|
||||
$sHost = (\in_array($iPort, array(80, 433))) ? $sName : $sName.':'.$iPort;
|
||||
}
|
||||
|
|
@ -144,7 +139,7 @@ class Http
|
|||
|
||||
if ($bWithRemoteUserData)
|
||||
{
|
||||
$sUser = \trim($this->GetServer('REMOTE_USER', ''));
|
||||
$sUser = \trim(static::GetServer('REMOTE_USER', ''));
|
||||
$sHost = (\strlen($sUser) ? $sUser.'@' : '').$sHost;
|
||||
}
|
||||
|
||||
|
|
@ -159,63 +154,68 @@ class Http
|
|||
public function GetClientIp(bool $bCheckProxy = false) : string
|
||||
{
|
||||
$sIp = '';
|
||||
if ($bCheckProxy && null !== $this->GetServer('HTTP_CLIENT_IP', null))
|
||||
if ($bCheckProxy && null !== static::GetServer('HTTP_CLIENT_IP', null))
|
||||
{
|
||||
$sIp = $this->GetServer('HTTP_CLIENT_IP', '');
|
||||
$sIp = static::GetServer('HTTP_CLIENT_IP', '');
|
||||
}
|
||||
else if ($bCheckProxy && null !== $this->GetServer('HTTP_X_FORWARDED_FOR', null))
|
||||
else if ($bCheckProxy && null !== static::GetServer('HTTP_X_FORWARDED_FOR', null))
|
||||
{
|
||||
$sIp = $this->GetServer('HTTP_X_FORWARDED_FOR', '');
|
||||
$sIp = static::GetServer('HTTP_X_FORWARDED_FOR', '');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sIp = $this->GetServer('REMOTE_ADDR', '');
|
||||
$sIp = static::GetServer('REMOTE_ADDR', '');
|
||||
}
|
||||
|
||||
return $sIp;
|
||||
}
|
||||
|
||||
public function ServerNotModifiedCache(int $iExpireTime, bool $bSetCacheHeader = true, string $sEtag = '') : bool
|
||||
{
|
||||
$bResult = false;
|
||||
if (0 < $iExpireTime)
|
||||
{
|
||||
$iUtcTimeStamp = \time();
|
||||
$sIfModifiedSince = $this->GetHeader('If-Modified-Since', '');
|
||||
if (0 === \strlen($sIfModifiedSince))
|
||||
{
|
||||
if ($bSetCacheHeader)
|
||||
{
|
||||
\header('Cache-Control: public');
|
||||
\header('Pragma: public');
|
||||
\header('Last-Modified: '.\gmdate('D, d M Y H:i:s', $iUtcTimeStamp - $iExpireTime).' UTC');
|
||||
\header('Expires: '.\gmdate('D, j M Y H:i:s', $iUtcTimeStamp + $iExpireTime).' UTC');
|
||||
|
||||
if (\strlen($sEtag))
|
||||
{
|
||||
\header('Etag: '.$sEtag);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
public static function checkETag(string $ETag) : void
|
||||
{
|
||||
$sIfNoneMatch = static::GetHeader('If-None-Match');
|
||||
if ($sIfNoneMatch && false !== \strpos($sIfNoneMatch, $ETag)) {
|
||||
static::StatusHeader(304);
|
||||
$bResult = true;
|
||||
exit;
|
||||
}
|
||||
$sIfMatch = static::GetHeader('If-Match');
|
||||
if ($sIfMatch && false === \strpos($sIfMatch, $ETag)) {
|
||||
static::StatusHeader(412);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
return $bResult;
|
||||
public static function setETag(string $ETag) : void
|
||||
{
|
||||
static::checkETag($ETag);
|
||||
\header("ETag: \"{$ETag}\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* @staticvar boolean $bCache
|
||||
*/
|
||||
public static function checkLastModified(int $mtime) : void
|
||||
{
|
||||
$sIfModifiedSince = static::GetHeader('If-Modified-Since');
|
||||
if ($sIfModifiedSince && $mtime <= \strtotime($sIfModifiedSince)) {
|
||||
static::StatusHeader(304);
|
||||
exit;
|
||||
}
|
||||
$sIfUnmodifiedSince = static::GetHeader('If-Unmodified-Since');
|
||||
if ($sIfUnmodifiedSince && $mtime > \strtotime($sIfUnmodifiedSince)) {
|
||||
static::StatusHeader(412);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
public static function setLastModified(int $mtime) : void
|
||||
{
|
||||
static::checkLastModified($mtime);
|
||||
\header('Last-Modified: '.\gmdate('D, d M Y H:i:s \G\M\T', $mtime)); # DATE_RFC1123
|
||||
}
|
||||
|
||||
private static $bCache = false;
|
||||
|
||||
public function ServerNoCache()
|
||||
{
|
||||
static $bCache = false;
|
||||
if (false === $bCache)
|
||||
{
|
||||
$bCache = true;
|
||||
if (!static::$bCache) {
|
||||
static::$bCache = true;
|
||||
\header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
\header('Last-Modified: '.\gmdate('D, d M Y H:i:s').' GMT');
|
||||
\header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0');
|
||||
|
|
@ -223,19 +223,14 @@ class Http
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @staticvar boolean $bCache
|
||||
*/
|
||||
public function ServerUseCache(string $sEtag, int $iLastModified, int $iExpires)
|
||||
public static function ServerUseCache(string $sEtag = '', int $iLastModified = 0, int $iExpires = 0) : void
|
||||
{
|
||||
static $bCache = false;
|
||||
if (false === $bCache)
|
||||
{
|
||||
$bCache = true;
|
||||
if (!static::$bCache) {
|
||||
static::$bCache = true;
|
||||
\header('Cache-Control: private');
|
||||
\header('ETag: '.$sEtag);
|
||||
\header('Last-Modified: '.\gmdate('D, d M Y H:i:s', $iLastModified).' UTC');
|
||||
\header('Expires: '.\gmdate('D, j M Y H:i:s', $iExpires).' UTC');
|
||||
$iExpires && \header('Expires: '.\gmdate('D, j M Y H:i:s', \time() + $iExpires).' UTC');
|
||||
$sEtag && static::setETag($sEtag);
|
||||
$iLastModified && static::setLastModified($iLastModified);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,6 +248,7 @@ class Http
|
|||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
412 => 'Precondition Failed',
|
||||
416 => 'Requested range not satisfiable',
|
||||
500 => 'Internal Server Error'
|
||||
);
|
||||
|
|
@ -271,7 +267,7 @@ class Http
|
|||
|
||||
public function GetPath() : string
|
||||
{
|
||||
$sUrl = \ltrim(\substr($this->GetServer('SCRIPT_NAME', ''), 0, \strrpos($this->GetServer('SCRIPT_NAME', ''), '/')), '/');
|
||||
$sUrl = \ltrim(\substr(static::GetServer('SCRIPT_NAME', ''), 0, \strrpos(static::GetServer('SCRIPT_NAME', ''), '/')), '/');
|
||||
return '' === $sUrl ? '/' : '/'.$sUrl.'/';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -147,10 +147,10 @@ class Actions
|
|||
'[SM:' . APP_VERSION . '][IP:'
|
||||
. $oHttp->GetClientIp($this->oConfig->Get('labs', 'http_client_ip_check_proxy', false))
|
||||
. '][PID:' . (\MailSo\Base\Utils::FunctionCallable('getmypid') ? \getmypid() : 'unknown')
|
||||
. '][' . $oHttp->GetServer('SERVER_SOFTWARE', '~')
|
||||
. '][' . \MailSo\Base\Http::GetServer('SERVER_SOFTWARE', '~')
|
||||
. '][' . (\MailSo\Base\Utils::FunctionCallable('php_sapi_name') ? \php_sapi_name() : '~')
|
||||
. '][Streams:' . \implode(',', \stream_get_transports())
|
||||
. '][' . $oHttp->GetMethod() . ' ' . $oHttp->GetScheme() . '://' . $oHttp->GetHost(false, false) . $oHttp->GetServer('REQUEST_URI', '') . ']'
|
||||
. '][' . $oHttp->GetMethod() . ' ' . $oHttp->GetScheme() . '://' . $oHttp->GetHost(false, false) . \MailSo\Base\Http::GetServer('REQUEST_URI', '') . ']'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1062,31 +1062,28 @@ class Actions
|
|||
|
||||
public function etag(string $sKey): string
|
||||
{
|
||||
return \md5('Etag:' . \md5($sKey . \md5($this->oConfig->Get('cache', 'index', ''))));
|
||||
return \md5($sKey . $this->oConfig->Get('cache', 'index', ''));
|
||||
}
|
||||
|
||||
public function cacheByKey(string $sKey, bool $bForce = false): bool
|
||||
{
|
||||
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);
|
||||
if ($sKey && ($bForce || ($this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true)))) {
|
||||
\MailSo\Base\Http::ServerUseCache(
|
||||
$this->etag($sKey),
|
||||
1382478804,
|
||||
$this->oConfig->Get('cache', 'http_expires', 3600)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$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)))) {
|
||||
$sIfNoneMatch = $this->Http()->GetHeader('If-None-Match', '');
|
||||
if ($this->etag($sKey) === $sIfNoneMatch) {
|
||||
\MailSo\Base\Http::StatusHeader(304);
|
||||
$this->cacheByKey($sKey);
|
||||
exit(0);
|
||||
}
|
||||
if ($sKey && ($bForce || ($this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true)))) {
|
||||
\MailSo\Base\Http::checkETag($this->etag($sKey));
|
||||
// $this->cacheByKey($sKey, $bForce);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ trait Localization
|
|||
private function getUserLanguagesFromHeader(): array
|
||||
{
|
||||
$aResult = $aList = array();
|
||||
$sAcceptLang = \strtolower($this->Http()->GetServer('HTTP_ACCEPT_LANGUAGE', 'en'));
|
||||
$sAcceptLang = \strtolower(\MailSo\Base\Http::GetServer('HTTP_ACCEPT_LANGUAGE', 'en'));
|
||||
if (!empty($sAcceptLang) && \preg_match_all('/([a-z]{1,8}(?:-[a-z]{1,8})?)(?:;q=([0-9.]+))?/', $sAcceptLang, $aList)) {
|
||||
$aResult = \array_combine($aList[1], $aList[2]);
|
||||
foreach ($aResult as $n => $v) {
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ trait Raw
|
|||
|
||||
$aValues = $this->getDecodedRawKeyValue($sRawKey);
|
||||
|
||||
$sRange = $this->Http()->GetHeader('Range');
|
||||
$sRange = \MailSo\Base\Http::GetHeader('Range');
|
||||
|
||||
$aMatch = array();
|
||||
$sRangeStart = $sRangeEnd = '';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue