mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-09 17:37:02 +03:00
Merge branch 'the-djmaze:master' into nc-theme
This commit is contained in:
commit
47ed699525
881 changed files with 48156 additions and 36724 deletions
|
|
@ -1,18 +1,89 @@
|
|||
{
|
||||
"imapHost": "localhost",
|
||||
"imapPort": 143,
|
||||
"imapSecure": 0,
|
||||
"imapShortLogin": false,
|
||||
"useSieve": false,
|
||||
"sieveHost": "localhost",
|
||||
"sievePort": 4190,
|
||||
"sieveSecure": 0,
|
||||
"smtpHost": "localhost",
|
||||
"smtpPort": 25,
|
||||
"smtpSecure": 0,
|
||||
"smtpShortLogin": false,
|
||||
"smtpAuth": true,
|
||||
"smtpSetSender": false,
|
||||
"smtpPhpMail": false,
|
||||
"name": "*",
|
||||
"IMAP": {
|
||||
"host": "localhost",
|
||||
"port": 143,
|
||||
"type": 0,
|
||||
"timeout": 300,
|
||||
"shortLogin": false,
|
||||
"sasl": [
|
||||
"SCRAM-SHA3-512",
|
||||
"SCRAM-SHA-512",
|
||||
"SCRAM-SHA-256",
|
||||
"SCRAM-SHA-1",
|
||||
"PLAIN",
|
||||
"LOGIN"
|
||||
],
|
||||
"ssl": {
|
||||
"verify_peer": false,
|
||||
"verify_peer_name": false,
|
||||
"allow_self_signed": false,
|
||||
"SNI_enabled": true,
|
||||
"disable_compression": true,
|
||||
"security_level": 1
|
||||
},
|
||||
"disable_list_status": false,
|
||||
"disable_metadata": false,
|
||||
"disable_move": false,
|
||||
"disable_sort": false,
|
||||
"disable_thread": false,
|
||||
"disable_binary": false,
|
||||
"use_expunge_all_on_delete": false,
|
||||
"fast_simple_search": true,
|
||||
"force_select": false,
|
||||
"message_all_headers": false,
|
||||
"message_list_limit": 0,
|
||||
"search_filter": ""
|
||||
},
|
||||
"SMTP": {
|
||||
"host": "localhost",
|
||||
"port": 25,
|
||||
"type": 0,
|
||||
"timeout": 60,
|
||||
"shortLogin": false,
|
||||
"sasl": [
|
||||
"SCRAM-SHA3-512",
|
||||
"SCRAM-SHA-512",
|
||||
"SCRAM-SHA-256",
|
||||
"SCRAM-SHA-1",
|
||||
"PLAIN",
|
||||
"LOGIN"
|
||||
],
|
||||
"ssl": {
|
||||
"verify_peer": false,
|
||||
"verify_peer_name": false,
|
||||
"allow_self_signed": false,
|
||||
"SNI_enabled": true,
|
||||
"disable_compression": true,
|
||||
"security_level": 1
|
||||
},
|
||||
"useAuth": false,
|
||||
"setSender": false,
|
||||
"usePhpMail": false
|
||||
},
|
||||
"Sieve": {
|
||||
"host": "localhost",
|
||||
"port": 4190,
|
||||
"type": 0,
|
||||
"timeout": 10,
|
||||
"shortLogin": false,
|
||||
"sasl": [
|
||||
"SCRAM-SHA3-512",
|
||||
"SCRAM-SHA-512",
|
||||
"SCRAM-SHA-256",
|
||||
"SCRAM-SHA-1",
|
||||
"PLAIN",
|
||||
"LOGIN"
|
||||
],
|
||||
"ssl": {
|
||||
"verify_peer": false,
|
||||
"verify_peer_name": false,
|
||||
"allow_self_signed": false,
|
||||
"SNI_enabled": true,
|
||||
"disable_compression": true,
|
||||
"security_level": 1
|
||||
},
|
||||
"enabled": false
|
||||
},
|
||||
"whiteList": ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
outlook.com,qq.com,yahoo.com
|
||||
outlook.com,qq.com,yahoo.com,gmail.com,hotmail.com
|
||||
|
|
@ -39,6 +39,11 @@ abstract class Collection extends \ArrayObject implements \JsonSerializable
|
|||
}
|
||||
}
|
||||
|
||||
public function keys() : array
|
||||
{
|
||||
return \array_keys($this->getArrayCopy());
|
||||
}
|
||||
|
||||
public function Add($mItem, bool $bToTop = false) : self
|
||||
{
|
||||
$this->append($mItem, $bToTop);
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ abstract class DateTimeHelper
|
|||
public static function GetUtcTimeZoneObject() : \DateTimeZone
|
||||
{
|
||||
static $oDateTimeZone = null;
|
||||
if (null === $oDateTimeZone)
|
||||
{
|
||||
if (null === $oDateTimeZone) {
|
||||
$oDateTimeZone = new \DateTimeZone('UTC');
|
||||
}
|
||||
return $oDateTimeZone;
|
||||
|
|
@ -37,8 +36,7 @@ abstract class DateTimeHelper
|
|||
public static function ParseRFC2822DateString(string $sDateTime) : int
|
||||
{
|
||||
$sDateTime = \trim($sDateTime);
|
||||
if (empty($sDateTime))
|
||||
{
|
||||
if (empty($sDateTime)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -54,13 +52,12 @@ abstract class DateTimeHelper
|
|||
public static function ParseInternalDateString(string $sDateTime) : int
|
||||
{
|
||||
$sDateTime = \trim($sDateTime);
|
||||
if (empty($sDateTime))
|
||||
{
|
||||
if (empty($sDateTime)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (\preg_match('/^[a-z]{2,4}, /i', $sDateTime)) // RFC2822 ~ "Thu, 10 Jun 2010 08:58:33 -0700 (PDT)"
|
||||
{
|
||||
// RFC2822 ~ "Thu, 10 Jun 2010 08:58:33 -0700 (PDT)"
|
||||
if (\preg_match('/^[a-z]{2,4}, /i', $sDateTime)) {
|
||||
return static::ParseRFC2822DateString($sDateTime);
|
||||
}
|
||||
|
||||
|
|
@ -74,8 +71,7 @@ abstract class DateTimeHelper
|
|||
public static function ParseDateStringType1(string $sDateTime) : int
|
||||
{
|
||||
$sDateTime = \trim($sDateTime);
|
||||
if (empty($sDateTime))
|
||||
{
|
||||
if (empty($sDateTime)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ abstract class HtmlUtils
|
|||
public static function BuildHtml(string $sHtml, array &$aFoundCids, array &$aFoundDataURL, array &$aFoundContentLocationUrls) : string
|
||||
{
|
||||
$bState = true;
|
||||
if (\MailSo\Base\Utils::FunctionCallable('libxml_use_internal_errors'))
|
||||
{
|
||||
if (\MailSo\Base\Utils::FunctionCallable('libxml_use_internal_errors')) {
|
||||
$bState = \libxml_use_internal_errors(true);
|
||||
}
|
||||
|
||||
|
|
@ -86,13 +85,11 @@ abstract class HtmlUtils
|
|||
|
||||
$oDoc->normalizeDocument();
|
||||
|
||||
if (\MailSo\Base\Utils::FunctionCallable('libxml_clear_errors'))
|
||||
{
|
||||
if (\MailSo\Base\Utils::FunctionCallable('libxml_clear_errors')) {
|
||||
\libxml_clear_errors();
|
||||
}
|
||||
|
||||
if (\MailSo\Base\Utils::FunctionCallable('libxml_use_internal_errors'))
|
||||
{
|
||||
if (\MailSo\Base\Utils::FunctionCallable('libxml_use_internal_errors')) {
|
||||
\libxml_use_internal_errors($bState);
|
||||
}
|
||||
|
||||
|
|
@ -100,63 +97,62 @@ 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'
|
||||
);
|
||||
|
||||
$aRemove = array();
|
||||
|
||||
$sIdRight = \md5(\microtime());
|
||||
|
||||
$aNodes = $oBody->getElementsByTagName('*');
|
||||
foreach ($aNodes as /* @var $oElement \DOMElement */ $oElement)
|
||||
{
|
||||
$sTagNameLower = \strtolower($oElement->nodeName);
|
||||
|
||||
if (\in_array($sTagNameLower, $aRemoveTags))
|
||||
{
|
||||
$aRemove[] = $oElement;
|
||||
continue;
|
||||
}
|
||||
|
||||
// images
|
||||
if ($oElement->hasAttribute('data-x-src-broken') || $oElement->hasAttribute('data-x-src-hidden')) {
|
||||
$aRemove[] = $oElement;
|
||||
continue;
|
||||
}
|
||||
if ($oElement->hasAttribute('data-x-src-cid')) {
|
||||
$sCid = $oElement->getAttribute('data-x-src-cid');
|
||||
$oElement->removeAttribute('data-x-src-cid');
|
||||
if (!empty($sCid)) {
|
||||
$aFoundCids[] = $sCid;
|
||||
$oElement->setAttribute('src', 'cid:'.$sCid);
|
||||
foreach ($aRemoveTags as $name) {
|
||||
$aNodes = $oBody->getElementsByTagName($name);
|
||||
foreach ($aNodes as /* @var $oElement \DOMElement */ $oElement) {
|
||||
if (isset($oElement->parentNode)) {
|
||||
@$oElement->parentNode->removeChild($oElement);
|
||||
}
|
||||
}
|
||||
if ($oElement->hasAttribute('data-x-src')) {
|
||||
$oElement->setAttribute('src', $oElement->getAttribute('data-x-src'));
|
||||
$oElement->removeAttribute('data-x-src');
|
||||
}
|
||||
}
|
||||
|
||||
// style attribute images
|
||||
$aCid = array();
|
||||
if ($oElement->hasAttribute('data-x-style-url')) {
|
||||
$aCid = \array_merge($aCid, \json_decode($oElement->getAttribute('data-x-style-url'), true));
|
||||
$oElement->removeAttribute('data-x-style-url');
|
||||
$xpath = new \DomXpath($oDoc);
|
||||
|
||||
foreach ($xpath->query('//*[@data-x-src-broken]') as $oElement) {
|
||||
if (isset($oElement->parentNode)) {
|
||||
@$oElement->parentNode->removeChild($oElement);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($xpath->query('//*[@data-x-src-hidden]') as $oElement) {
|
||||
if (isset($oElement->parentNode)) {
|
||||
@$oElement->parentNode->removeChild($oElement);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($xpath->query('//*[@data-x-src-cid]') as $oElement) {
|
||||
$sCid = $oElement->getAttribute('data-x-src-cid');
|
||||
$oElement->removeAttribute('data-x-src-cid');
|
||||
if (!empty($sCid)) {
|
||||
$aFoundCids[] = $sCid;
|
||||
$oElement->setAttribute('src', 'cid:'.$sCid);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($xpath->query('//*[@data-x-src]') as $oElement) {
|
||||
$oElement->setAttribute('src', $oElement->getAttribute('data-x-src'));
|
||||
$oElement->removeAttribute('data-x-src');
|
||||
}
|
||||
|
||||
// style attribute images
|
||||
foreach ($xpath->query('//*[@data-x-style-url]') as $oElement) {
|
||||
$aCid = \json_decode($oElement->getAttribute('data-x-style-url'), true);
|
||||
$oElement->removeAttribute('data-x-style-url');
|
||||
if ($aCid) {
|
||||
foreach ($aCid as $sCidName => $sCid) {
|
||||
$sCidName = \strtolower(\preg_replace('/([A-Z])/', '-\1', $sCidName));
|
||||
if (\in_array($sCidName, array('background-image', 'list-style-image', 'content')))
|
||||
{
|
||||
if (\in_array($sCidName, array('background-image', 'list-style-image', 'content'))) {
|
||||
$sStyles = $oElement->hasAttribute('style')
|
||||
? \trim(\trim($oElement->getAttribute('style')), ';')
|
||||
: '';
|
||||
|
||||
$sBack = $sCidName.':url(cid:'.$sCid.')';
|
||||
$sStyles = \preg_replace('/'.\preg_quote($sCidName).'\\s*:\\s*[^;]+/i', $sBack, $sStyles);
|
||||
if (false === \strpos($sStyles, $sBack))
|
||||
{
|
||||
if (false === \strpos($sStyles, $sBack)) {
|
||||
$sStyles .= ";{$sBack}";
|
||||
}
|
||||
|
||||
|
|
@ -165,35 +161,28 @@ abstract class HtmlUtils
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all remaining data-* attributes
|
||||
// Remove all remaining data-* attributes
|
||||
foreach ($xpath->query('//*[@*[starts-with(name(), "data-")]]') as $oElement) {
|
||||
$sTagNameLower = \strtolower($oElement->nodeName);
|
||||
if ($oElement->hasAttributes()) {
|
||||
foreach ($oElement->attributes as $oAttr) {
|
||||
if ('data-' === \substr(\strtolower($oAttr->nodeName), 0, 5))
|
||||
{
|
||||
if ('data-' === \substr(\strtolower($oAttr->nodeName), 0, 5)) {
|
||||
$oElement->removeAttribute($oAttr->nodeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ('img' === $sTagNameLower)
|
||||
{
|
||||
$sSrc = $oElement->getAttribute('src');
|
||||
if ('data:image/' === \strtolower(\substr($sSrc, 0, 11)))
|
||||
{
|
||||
$sHash = \md5($sSrc) . '@' . $sIdRight;
|
||||
$aFoundDataURL[$sHash] = $sSrc;
|
||||
|
||||
$oElement->setAttribute('src', 'cid:'.$sHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($aRemove as /* @var $oElement \DOMElement */ $oElement)
|
||||
{
|
||||
if (isset($oElement->parentNode))
|
||||
{
|
||||
@$oElement->parentNode->removeChild($oElement);
|
||||
$sIdRight = \md5(\microtime());
|
||||
$aNodes = $oBody->getElementsByTagName('img');
|
||||
foreach ($aNodes as /* @var $oElement \DOMElement */ $oElement) {
|
||||
$sSrc = $oElement->getAttribute('src');
|
||||
if ('data:image/' === \strtolower(\substr($sSrc, 0, 11))) {
|
||||
$sHash = \md5($sSrc) . '@' . $sIdRight;
|
||||
$aFoundDataURL[$sHash] = $sSrc;
|
||||
$oElement->setAttribute('src', 'cid:'.$sHash);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -201,87 +190,6 @@ abstract class HtmlUtils
|
|||
. $oDoc->saveHTML($oBody) . '</html>';
|
||||
}
|
||||
|
||||
public static function ConvertPlainToHtml(string $sText, bool $bLinksWithTargetBlank = true) : string
|
||||
{
|
||||
$sText = \trim($sText);
|
||||
if (!\strlen($sText))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
$sText = (new \MailSo\Base\LinkFinder)
|
||||
->Text($sText)
|
||||
->UseDefaultWrappers($bLinksWithTargetBlank)
|
||||
->CompileText()
|
||||
;
|
||||
|
||||
$sText = \str_replace("\r", '', $sText);
|
||||
|
||||
$aText = \explode("\n", $sText);
|
||||
unset($sText);
|
||||
|
||||
$bIn = false;
|
||||
$bDo = true;
|
||||
do
|
||||
{
|
||||
$bDo = false;
|
||||
$aNextText = array();
|
||||
foreach ($aText as $sTextLine)
|
||||
{
|
||||
$bStart = 0 === \strpos(\ltrim($sTextLine), '>');
|
||||
if ($bStart && !$bIn)
|
||||
{
|
||||
$bDo = true;
|
||||
$bIn = true;
|
||||
$aNextText[] = '<blockquote>';
|
||||
$aNextText[] = \substr(\ltrim($sTextLine), 4);
|
||||
}
|
||||
else if (!$bStart && $bIn)
|
||||
{
|
||||
$bIn = false;
|
||||
$aNextText[] = '</blockquote>';
|
||||
$aNextText[] = $sTextLine;
|
||||
}
|
||||
else if ($bStart && $bIn)
|
||||
{
|
||||
$aNextText[] = \substr(\ltrim($sTextLine), 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aNextText[] = $sTextLine;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bIn)
|
||||
{
|
||||
$bIn = false;
|
||||
$aNextText[] = '</blockquote>';
|
||||
}
|
||||
|
||||
$aText = $aNextText;
|
||||
}
|
||||
while ($bDo);
|
||||
|
||||
$sText = \join("\n", $aText);
|
||||
unset($aText);
|
||||
|
||||
$sText = \preg_replace('/[\n][ ]+/', "\n", $sText);
|
||||
// $sText = \preg_replace('/[\s]+([\s])/', '\\1', $sText);
|
||||
|
||||
$sText = \preg_replace('/<blockquote>[\s]+/i', '<blockquote>', $sText);
|
||||
$sText = \preg_replace('/[\s]+<\/blockquote>/i', '</blockquote>', $sText);
|
||||
|
||||
$sText = \preg_replace('/<\/blockquote>([\n]{0,2})<blockquote>/i', '\\1', $sText);
|
||||
$sText = \preg_replace('/[\n]{3,}/', "\n\n", $sText);
|
||||
|
||||
$sText = \strtr($sText, array(
|
||||
"\t" => "\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0",
|
||||
' ' => "\xC2\xA0\xC2\xA0"
|
||||
));
|
||||
|
||||
return \nl2br($sText);
|
||||
}
|
||||
|
||||
public static function ConvertHtmlToPlain(string $sText) : string
|
||||
{
|
||||
$sText = \MailSo\Base\Utils::StripSpaces($sText);
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ class Http
|
|||
public static function SingletonInstance() : self
|
||||
{
|
||||
static $oInstance = null;
|
||||
if (null === $oInstance)
|
||||
{
|
||||
if (null === $oInstance) {
|
||||
$oInstance = new self;
|
||||
}
|
||||
|
||||
|
|
@ -36,14 +35,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
|
||||
|
|
@ -65,9 +64,8 @@ class Http
|
|||
|
||||
public function IsLocalhost(string $sValueToCheck = '') : bool
|
||||
{
|
||||
if (empty($sValueToCheck))
|
||||
{
|
||||
$sValueToCheck = $this->GetServer('REMOTE_ADDR', '');
|
||||
if (empty($sValueToCheck)) {
|
||||
$sValueToCheck = static::GetServer('REMOTE_ADDR', '');
|
||||
}
|
||||
|
||||
return $this->CheckLocalhost($sValueToCheck);
|
||||
|
|
@ -76,29 +74,23 @@ class Http
|
|||
public function GetRawBody() : string
|
||||
{
|
||||
static $sRawBody = null;
|
||||
if (null === $sRawBody)
|
||||
{
|
||||
if (null === $sRawBody) {
|
||||
$sBody = \file_get_contents('php://input');
|
||||
$sRawBody = (false !== $sBody) ? $sBody : '';
|
||||
}
|
||||
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 (!\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,155 +101,128 @@ class Http
|
|||
|
||||
public function IsSecure(bool $bCheckProxy = true) : bool
|
||||
{
|
||||
$sHttps = \strtolower($this->GetServer('HTTPS', ''));
|
||||
if ('on' === $sHttps || ('' === $sHttps && '443' === (string) $this->GetServer('SERVER_PORT', '')))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($bCheckProxy && (
|
||||
('https' === \strtolower($this->GetServer('HTTP_X_FORWARDED_PROTO', ''))) ||
|
||||
('on' === \strtolower($this->GetServer('HTTP_X_FORWARDED_SSL', '')))
|
||||
))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
$sHttps = \strtolower(static::GetServer('HTTPS', ''));
|
||||
return ('on' === $sHttps || ('' === $sHttps && '443' === (string) static::GetServer('SERVER_PORT', '')))
|
||||
|| ($bCheckProxy && (
|
||||
('https' === \strtolower(static::GetServer('HTTP_X_FORWARDED_PROTO', ''))) ||
|
||||
('on' === \strtolower(static::GetServer('HTTP_X_FORWARDED_SSL', '')))
|
||||
));
|
||||
}
|
||||
|
||||
public function GetHost(bool $bWithRemoteUserData = false, bool $bWithoutWWW = true, bool $bWithoutPort = false) : string
|
||||
public function GetHost(bool $bWithoutWWW = true, bool $bWithoutPort = false) : string
|
||||
{
|
||||
$sHost = $this->GetServer('HTTP_HOST', '');
|
||||
if (!\strlen($sHost))
|
||||
{
|
||||
$sName = $this->GetServer('SERVER_NAME');
|
||||
$iPort = (int) $this->GetServer('SERVER_PORT', 80);
|
||||
$sHost = static::GetServer('HTTP_HOST', '');
|
||||
if (!\strlen($sHost)) {
|
||||
$sName = static::GetServer('SERVER_NAME', '');
|
||||
$iPort = (int) static::GetServer('SERVER_PORT', 80);
|
||||
|
||||
$sHost = (\in_array($iPort, array(80, 433))) ? $sName : $sName.':'.$iPort;
|
||||
}
|
||||
|
||||
if ($bWithoutWWW)
|
||||
{
|
||||
$sHost = 'www.' === \substr(\strtolower($sHost), 0, 4) ? \substr($sHost, 4) : $sHost;
|
||||
if ($bWithoutWWW && 'www.' === \substr(\strtolower($sHost), 0, 4)) {
|
||||
$sHost = \substr($sHost, 4);
|
||||
}
|
||||
|
||||
if ($bWithRemoteUserData)
|
||||
{
|
||||
$sUser = \trim($this->GetServer('REMOTE_USER', ''));
|
||||
$sHost = (\strlen($sUser) ? $sUser.'@' : '').$sHost;
|
||||
}
|
||||
|
||||
if ($bWithoutPort)
|
||||
{
|
||||
$sHost = \preg_replace('/:\d+$/', '', $sHost);
|
||||
}
|
||||
|
||||
return $sHost;
|
||||
return $bWithoutPort ? \preg_replace('/:\d+$/', '', $sHost) : $sHost;
|
||||
}
|
||||
|
||||
public function GetClientIp(bool $bCheckProxy = false) : string
|
||||
{
|
||||
$sIp = '';
|
||||
if ($bCheckProxy && null !== $this->GetServer('HTTP_CLIENT_IP', null))
|
||||
{
|
||||
$sIp = $this->GetServer('HTTP_CLIENT_IP', '');
|
||||
if ($bCheckProxy && null !== static::GetServer('HTTP_CLIENT_IP', null)) {
|
||||
return static::GetServer('HTTP_CLIENT_IP', '');
|
||||
}
|
||||
else if ($bCheckProxy && null !== $this->GetServer('HTTP_X_FORWARDED_FOR', null))
|
||||
{
|
||||
$sIp = $this->GetServer('HTTP_X_FORWARDED_FOR', '');
|
||||
if ($bCheckProxy && null !== static::GetServer('HTTP_X_FORWARDED_FOR', null)) {
|
||||
return static::GetServer('HTTP_X_FORWARDED_FOR', '');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sIp = $this->GetServer('REMOTE_ADDR', '');
|
||||
}
|
||||
|
||||
return $sIp;
|
||||
return static::GetServer('REMOTE_ADDR', '');
|
||||
}
|
||||
|
||||
public function ServerNotModifiedCache(int $iExpireTime, bool $bSetCacheHeader = true, string $sEtag = '') : bool
|
||||
public static function checkETag(string $ETag) : void
|
||||
{
|
||||
$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
|
||||
{
|
||||
static::StatusHeader(304);
|
||||
$bResult = true;
|
||||
}
|
||||
// $ETag . APP_VERSION
|
||||
$sIfNoneMatch = static::GetHeader('If-None-Match');
|
||||
if ($sIfNoneMatch && false !== \strpos($sIfNoneMatch, $ETag)) {
|
||||
static::StatusHeader(304);
|
||||
exit;
|
||||
}
|
||||
$sIfMatch = static::GetHeader('If-Match');
|
||||
if ($sIfMatch && false === \strpos($sIfMatch, $ETag)) {
|
||||
static::StatusHeader(412);
|
||||
exit;
|
||||
}
|
||||
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @staticvar boolean $bCache
|
||||
*/
|
||||
public static function setETag(string $ETag) : void
|
||||
{
|
||||
// $ETag . APP_VERSION
|
||||
static::checkETag($ETag);
|
||||
\header("ETag: \"{$ETag}\"");
|
||||
}
|
||||
|
||||
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');
|
||||
\header('Cache-Control: no-store');
|
||||
\header('Pragma: no-cache');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
public static function StatusHeader(int $iStatus, string $sCustomStatusText = '') : void
|
||||
{
|
||||
if (99 < $iStatus)
|
||||
{
|
||||
if (99 < $iStatus) {
|
||||
$aStatus = array(
|
||||
200 => 'OK',
|
||||
206 => 'Partial Content',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
304 => 'Not Modified',
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
412 => 'Precondition Failed',
|
||||
416 => 'Requested range not satisfiable',
|
||||
500 => 'Internal Server Error'
|
||||
);
|
||||
|
||||
$sHeaderText = (0 === \strlen($sCustomStatusText) && isset($aStatus[$iStatus]) ? $aStatus[$iStatus] : $sCustomStatusText);
|
||||
$sHeaderText = (!\strlen($sCustomStatusText) && isset($aStatus[$iStatus]) ? $aStatus[$iStatus] : $sCustomStatusText);
|
||||
|
||||
\http_response_code($iStatus);
|
||||
if (isset($_SERVER['SERVER_PROTOCOL'])) {
|
||||
|
|
@ -269,9 +234,15 @@ class Http
|
|||
}
|
||||
}
|
||||
|
||||
public static function Location(string $sUrl, int $iStatus = 302): void
|
||||
{
|
||||
static::StatusHeader($iStatus);
|
||||
\header('Location: ' . $sUrl);
|
||||
}
|
||||
|
||||
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.'/';
|
||||
}
|
||||
|
||||
|
|
@ -282,6 +253,6 @@ class Http
|
|||
|
||||
public function GetFullUrl() : string
|
||||
{
|
||||
return $this->GetScheme().'://'.$this->GetHost(true, false).$this->GetPath();
|
||||
return $this->GetScheme().'://'.$this->GetHost(false).$this->GetPath();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,259 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2014 Usenko Timur
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo\Base;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Base
|
||||
*/
|
||||
class LinkFinder
|
||||
{
|
||||
/**
|
||||
* @const
|
||||
*/
|
||||
const OPEN_LINK = '@#@link{';
|
||||
const CLOSE_LINK = '}link@#@';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aPrepearPlainStringUrls;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sText;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $fLinkWrapper;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iHtmlSpecialCharsFlags;
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $fMailWrapper;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iOptimizationLimit;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->iHtmlSpecialCharsFlags = (\defined('ENT_QUOTES') && \defined('ENT_SUBSTITUTE') && \defined('ENT_HTML401'))
|
||||
? ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 : ENT_QUOTES;
|
||||
|
||||
if (\defined('ENT_IGNORE'))
|
||||
{
|
||||
$this->iHtmlSpecialCharsFlags |= ENT_IGNORE;
|
||||
}
|
||||
|
||||
$this->iOptimizationLimit = 300000;
|
||||
|
||||
$this->Clear();
|
||||
}
|
||||
|
||||
public function Clear() : self
|
||||
{
|
||||
$this->aPrepearPlainStringUrls = array();
|
||||
$this->fLinkWrapper = null;
|
||||
$this->fMailWrapper = null;
|
||||
$this->sText = '';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function Text(string $sText) : self
|
||||
{
|
||||
$this->sText = $sText;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $fLinkWrapper
|
||||
*/
|
||||
public function LinkWrapper($fLinkWrapper) : self
|
||||
{
|
||||
$this->fLinkWrapper = $fLinkWrapper;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $fMailWrapper
|
||||
*/
|
||||
public function MailWrapper($fMailWrapper) : self
|
||||
{
|
||||
$this->fMailWrapper = $fMailWrapper;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function UseDefaultWrappers(bool $bAddTargetBlank = false) : self
|
||||
{
|
||||
$this->fLinkWrapper = function ($sLink) use ($bAddTargetBlank) {
|
||||
|
||||
$sNameLink = $sLink;
|
||||
if (!\preg_match('/^[a-z]{3,5}\:\/\//i', \ltrim($sLink)))
|
||||
{
|
||||
$sLink = 'https://'.\ltrim($sLink);
|
||||
}
|
||||
|
||||
return '<a '.($bAddTargetBlank ? 'target="_blank" ': '').'href="'.$sLink.'">'.$sNameLink.'</a>';
|
||||
};
|
||||
|
||||
$this->fMailWrapper = function ($sEmail) use ($bAddTargetBlank) {
|
||||
return '<a '.($bAddTargetBlank ? 'target="_blank" ': '').'href="mailto:'.$sEmail.'">'.$sEmail.'</a>';
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function CompileText(bool $bUseHtmlSpecialChars = true) : string
|
||||
{
|
||||
$sText = \substr($this->sText, 0, $this->iOptimizationLimit);
|
||||
$sSubText = \substr($this->sText, $this->iOptimizationLimit);
|
||||
|
||||
$this->aPrepearPlainStringUrls = array();
|
||||
if (null !== $this->fLinkWrapper && \is_callable($this->fLinkWrapper))
|
||||
{
|
||||
$sText = $this->findLinks($sText, $this->fLinkWrapper);
|
||||
}
|
||||
|
||||
if (null !== $this->fMailWrapper && \is_callable($this->fMailWrapper))
|
||||
{
|
||||
$sText = $this->findMails($sText, $this->fMailWrapper);
|
||||
}
|
||||
|
||||
$sResult = '';
|
||||
if ($bUseHtmlSpecialChars)
|
||||
{
|
||||
$sResult = \htmlentities($sText.$sSubText, $this->iHtmlSpecialCharsFlags, 'UTF-8');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sResult = $sText.$sSubText;
|
||||
}
|
||||
|
||||
unset($sText, $sSubText);
|
||||
|
||||
if (\count($this->aPrepearPlainStringUrls))
|
||||
{
|
||||
$aPrepearPlainStringUrls = $this->aPrepearPlainStringUrls;
|
||||
$sResult = \preg_replace_callback('/'.\preg_quote(static::OPEN_LINK, '/').
|
||||
'([\d]+)'.\preg_quote(static::CLOSE_LINK, '/').'/',
|
||||
function ($aMatches) use ($aPrepearPlainStringUrls) {
|
||||
$iIndex = (int) $aMatches[1];
|
||||
return isset($aPrepearPlainStringUrls[$iIndex]) ? $aPrepearPlainStringUrls[$iIndex] : '';
|
||||
}, $sResult);
|
||||
|
||||
$this->aPrepearPlainStringUrls = array();
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $fWrapper
|
||||
*/
|
||||
private function findLinks(string $sText, $fWrapper) : string
|
||||
{
|
||||
$sPattern = '/([\W]|^)((?:https?:\/\/)|(?:svn:\/\/)|(?:git:\/\/)|(?:s?ftps?:\/\/)|(?:www\.))'.
|
||||
'((\S+?)(\\/)?)((?:>)?|[^\w\=\\/;\(\)\[\]]*?)(?=<|\s|$)/imu';
|
||||
|
||||
$aPrepearPlainStringUrls = $this->aPrepearPlainStringUrls;
|
||||
$sText = \preg_replace_callback($sPattern, function ($aMatch) use ($fWrapper, &$aPrepearPlainStringUrls) {
|
||||
|
||||
if (\is_array($aMatch) && 6 < \count($aMatch))
|
||||
{
|
||||
while (\in_array($sChar = \substr($aMatch[3], -1), array(']', ')')))
|
||||
{
|
||||
if (\substr_count($aMatch[3], ']' === $sChar ? '[': '(') - \substr_count($aMatch[3], $sChar) < 0)
|
||||
{
|
||||
$aMatch[3] = \substr($aMatch[3], 0, -1);
|
||||
$aMatch[6] = (']' === $sChar ? ']': ')').$aMatch[6];
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$sLinkWithWrap = $fWrapper($aMatch[2].$aMatch[3]);
|
||||
if (\is_string($sLinkWithWrap) && \strlen($sLinkWithWrap))
|
||||
{
|
||||
$aPrepearPlainStringUrls[] = \stripslashes($sLinkWithWrap);
|
||||
return $aMatch[1].
|
||||
static::OPEN_LINK.
|
||||
(\count($aPrepearPlainStringUrls) - 1).
|
||||
static::CLOSE_LINK.
|
||||
$aMatch[6];
|
||||
}
|
||||
|
||||
return $aMatch[0];
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
}, $sText);
|
||||
|
||||
if (\count($aPrepearPlainStringUrls))
|
||||
{
|
||||
$this->aPrepearPlainStringUrls = $aPrepearPlainStringUrls;
|
||||
}
|
||||
|
||||
return $sText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $fWrapper
|
||||
*/
|
||||
private function findMails(string $sText, $fWrapper) : string
|
||||
{
|
||||
$sPattern = '/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/';
|
||||
|
||||
$aPrepearPlainStringUrls = $this->aPrepearPlainStringUrls;
|
||||
$sText = \preg_replace_callback($sPattern, function ($aMatch) use ($fWrapper, &$aPrepearPlainStringUrls) {
|
||||
|
||||
if (\is_array($aMatch) && isset($aMatch[1]))
|
||||
{
|
||||
$sMailWithWrap = $fWrapper($aMatch[1]);
|
||||
if (\is_string($sMailWithWrap) && \strlen($sMailWithWrap))
|
||||
{
|
||||
$aPrepearPlainStringUrls[] = \stripslashes($sMailWithWrap);
|
||||
return static::OPEN_LINK.
|
||||
(\count($aPrepearPlainStringUrls) - 1).
|
||||
static::CLOSE_LINK;
|
||||
}
|
||||
|
||||
return $aMatch[1];
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
}, $sText);
|
||||
|
||||
if (\count($aPrepearPlainStringUrls))
|
||||
{
|
||||
$this->aPrepearPlainStringUrls = $aPrepearPlainStringUrls;
|
||||
}
|
||||
|
||||
return $sText;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,10 +17,7 @@ namespace MailSo\Base;
|
|||
*/
|
||||
abstract class ResourceRegistry
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $Resources = array();
|
||||
public static array $Resources = array();
|
||||
|
||||
/**
|
||||
* @staticvar bool $bInited
|
||||
|
|
@ -28,16 +25,12 @@ abstract class ResourceRegistry
|
|||
private static function regResourcesShutdownFunc() : void
|
||||
{
|
||||
static $bInited = false;
|
||||
if (!$bInited)
|
||||
{
|
||||
if (!$bInited) {
|
||||
$bInited = true;
|
||||
\register_shutdown_function(function () {
|
||||
if (\is_array(static::$Resources))
|
||||
{
|
||||
foreach (\array_keys(static::$Resources) as $sKey)
|
||||
{
|
||||
if (\is_resource(static::$Resources[$sKey]))
|
||||
{
|
||||
if (\is_array(static::$Resources)) {
|
||||
foreach (\array_keys(static::$Resources) as $sKey) {
|
||||
if (\is_resource(static::$Resources[$sKey])) {
|
||||
\fclose(static::$Resources[$sKey]);
|
||||
}
|
||||
static::$Resources[$sKey] = null;
|
||||
|
|
@ -57,8 +50,7 @@ abstract class ResourceRegistry
|
|||
self::regResourcesShutdownFunc();
|
||||
|
||||
$oResult = \fopen('php://temp/maxmemory:'.($iMemoryMaxInMb * 1024 * 1024), 'r+b');
|
||||
if (\is_resource($oResult))
|
||||
{
|
||||
if (\is_resource($oResult)) {
|
||||
static::$Resources[(string) $oResult] = $oResult;
|
||||
return $oResult;
|
||||
}
|
||||
|
|
@ -72,8 +64,7 @@ abstract class ResourceRegistry
|
|||
public static function CreateMemoryResourceFromString(string $sString)
|
||||
{
|
||||
$oResult = self::CreateMemoryResource();
|
||||
if (\is_resource($oResult))
|
||||
{
|
||||
if (\is_resource($oResult)) {
|
||||
\fwrite($oResult, $sString);
|
||||
\rewind($oResult);
|
||||
}
|
||||
|
|
@ -86,18 +77,15 @@ abstract class ResourceRegistry
|
|||
*/
|
||||
public static function CloseMemoryResource(&$rResource) : void
|
||||
{
|
||||
if (\is_resource($rResource))
|
||||
{
|
||||
if (\is_resource($rResource)) {
|
||||
$sKey = (string) $rResource;
|
||||
if (isset(static::$Resources[$sKey]))
|
||||
{
|
||||
if (isset(static::$Resources[$sKey])) {
|
||||
\fclose(static::$Resources[$sKey]);
|
||||
static::$Resources[$sKey] = null;
|
||||
unset(static::$Resources[$sKey]);
|
||||
}
|
||||
|
||||
if (\is_resource($rResource))
|
||||
{
|
||||
if (\is_resource($rResource)) {
|
||||
\fclose($rResource);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,55 +19,34 @@ namespace MailSo\Base\StreamWrappers;
|
|||
*/
|
||||
class Binary
|
||||
{
|
||||
/** @var resource|null */
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const STREAM_NAME = 'mailsobinary';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $aStreams = array();
|
||||
private static array $aStreams = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $aRememberStreams = array();
|
||||
private static array $aRememberStreams = array();
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $rStream;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sFromEncoding;
|
||||
private string $sFromEncoding;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sToEncoding;
|
||||
private string $sToEncoding;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sFunctionName;
|
||||
private string $sFunctionName;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iPos;
|
||||
private int $iPos;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sBuffer;
|
||||
private string $sBuffer;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sReadEndBuffer;
|
||||
private string $sReadEndBuffer;
|
||||
|
||||
public static function GetInlineDecodeOrEncodeFunctionName(string $sContentTransferEncoding, bool $bDecode = true) : string
|
||||
{
|
||||
|
|
@ -93,8 +72,7 @@ class Binary
|
|||
$sEndBuffer = '';
|
||||
$iQuotedPrintableLen = \strlen($sEncodedString);
|
||||
$iLastSpace = \strrpos($sEncodedString, ' ');
|
||||
if (false !== $iLastSpace && $iLastSpace + 1 < $iQuotedPrintableLen)
|
||||
{
|
||||
if (false !== $iLastSpace && $iLastSpace + 1 < $iQuotedPrintableLen) {
|
||||
$sEndBuffer = \substr($sEncodedString, $iLastSpace + 1);
|
||||
$sEncodedString = \substr($sEncodedString, 0, $iLastSpace + 1);
|
||||
}
|
||||
|
|
@ -106,10 +84,8 @@ class Binary
|
|||
*/
|
||||
public static function IsStreamRemembed($rStream) : bool
|
||||
{
|
||||
foreach (self::$aRememberStreams as $rRem)
|
||||
{
|
||||
if ($rStream === $rRem)
|
||||
{
|
||||
foreach (self::$aRememberStreams as $rRem) {
|
||||
if ($rStream === $rRem) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -122,8 +98,7 @@ class Binary
|
|||
*/
|
||||
public static function RememberStream($rStream)
|
||||
{
|
||||
if (!self::IsStreamRemembed($rStream))
|
||||
{
|
||||
if (!self::IsStreamRemembed($rStream)) {
|
||||
self::$aRememberStreams[] = $rStream;
|
||||
}
|
||||
}
|
||||
|
|
@ -136,15 +111,13 @@ class Binary
|
|||
public static function CreateStream($rStream,
|
||||
string $sUtilsDecodeOrEncodeFunctionName = null, string $sFromEncoding = null, string $sToEncoding = null)
|
||||
{
|
||||
if (null === $sUtilsDecodeOrEncodeFunctionName || !\strlen($sUtilsDecodeOrEncodeFunctionName))
|
||||
{
|
||||
if (null === $sUtilsDecodeOrEncodeFunctionName || !\strlen($sUtilsDecodeOrEncodeFunctionName)) {
|
||||
$sUtilsDecodeOrEncodeFunctionName = 'InlineNullDecode';
|
||||
}
|
||||
|
||||
$sHashName = \md5(\microtime(true).\rand(1000, 9999));
|
||||
|
||||
if (null !== $sFromEncoding && null !== $sToEncoding && $sFromEncoding !== $sToEncoding)
|
||||
{
|
||||
if (null !== $sFromEncoding && null !== $sToEncoding && $sFromEncoding !== $sToEncoding) {
|
||||
$rStream = self::CreateStream($rStream, $sUtilsDecodeOrEncodeFunctionName);
|
||||
$sUtilsDecodeOrEncodeFunctionName = 'InlineConvertDecode';
|
||||
}
|
||||
|
|
@ -213,27 +186,19 @@ class Binary
|
|||
$sReturn = '';
|
||||
$sFunctionName = $this->sFunctionName;
|
||||
|
||||
if ($iCount > 0)
|
||||
{
|
||||
if ($iCount < \strlen($this->sBuffer))
|
||||
{
|
||||
if ($iCount > 0) {
|
||||
if ($iCount < \strlen($this->sBuffer)) {
|
||||
$sReturn = \substr($this->sBuffer, 0, $iCount);
|
||||
$this->sBuffer = \substr($this->sBuffer, $iCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sReturn = $this->sBuffer;
|
||||
while ($iCount > 0)
|
||||
{
|
||||
if (\feof($this->rStream))
|
||||
{
|
||||
if (!\strlen($this->sBuffer.$sReturn))
|
||||
{
|
||||
while ($iCount > 0) {
|
||||
if (\feof($this->rStream)) {
|
||||
if (!\strlen($this->sBuffer.$sReturn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\strlen($this->sReadEndBuffer))
|
||||
{
|
||||
if (\strlen($this->sReadEndBuffer)) {
|
||||
$sReturn .= self::$sFunctionName($this->sReadEndBuffer,
|
||||
$this->sReadEndBuffer, $this->sFromEncoding, $this->sToEncoding);
|
||||
|
||||
|
|
@ -242,12 +207,9 @@ class Binary
|
|||
|
||||
$iCount = 0;
|
||||
$this->sBuffer = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sReadResult = \fread($this->rStream, 8192);
|
||||
if (false === $sReadResult)
|
||||
{
|
||||
if (false === $sReadResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -255,14 +217,11 @@ class Binary
|
|||
$this->sReadEndBuffer, $this->sFromEncoding, $this->sToEncoding);
|
||||
|
||||
$iDecodeLen = \strlen($sReadResult);
|
||||
if ($iCount < $iDecodeLen)
|
||||
{
|
||||
if ($iCount < $iDecodeLen) {
|
||||
$this->sBuffer = \substr($sReturn, $iCount);
|
||||
$sReturn = \substr($sReturn, 0, $iCount);
|
||||
$iCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$iCount -= $iDecodeLen;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,30 +18,24 @@ namespace MailSo\Base\StreamWrappers;
|
|||
*/
|
||||
class Literal
|
||||
{
|
||||
/** @var resource|null */
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const STREAM_NAME = 'mailsoliteral';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $aStreams = array();
|
||||
private static array $aStreams = array();
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $rStream;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iSize;
|
||||
private int $iSize;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iPos;
|
||||
private int $iPos;
|
||||
|
||||
/**
|
||||
* @param resource $rStream
|
||||
|
|
@ -92,20 +86,16 @@ class Literal
|
|||
public function stream_read(int $iCount) : string
|
||||
{
|
||||
$sResult = false;
|
||||
if ($this->iSize < $this->iPos + $iCount)
|
||||
{
|
||||
if ($this->iSize < $this->iPos + $iCount) {
|
||||
$iCount = $this->iSize - $this->iPos;
|
||||
}
|
||||
|
||||
if ($iCount > 0)
|
||||
{
|
||||
if ($iCount > 0) {
|
||||
$sReadResult = '';
|
||||
$iRead = $iCount;
|
||||
while (0 < $iRead)
|
||||
{
|
||||
while (0 < $iRead) {
|
||||
$sAddRead = \fread($this->rStream, $iRead);
|
||||
if (false === $sAddRead)
|
||||
{
|
||||
if (false === $sAddRead) {
|
||||
$sReadResult = false;
|
||||
break;
|
||||
}
|
||||
|
|
@ -115,8 +105,7 @@ class Literal
|
|||
$this->iPos += \strlen($sAddRead);
|
||||
}
|
||||
|
||||
if (false !== $sReadResult)
|
||||
{
|
||||
if (false !== $sReadResult) {
|
||||
$sResult = $sReadResult;
|
||||
}
|
||||
}
|
||||
|
|
@ -160,6 +149,8 @@ class Literal
|
|||
|
||||
public function stream_seek() : bool
|
||||
{
|
||||
// $this->iPos = $offset;
|
||||
// \fseek($this->rStream, $offset, $whence);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,40 +19,25 @@ namespace MailSo\Base\StreamWrappers;
|
|||
*/
|
||||
class SubStreams
|
||||
{
|
||||
/** @var resource|null */
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
const STREAM_NAME = 'mailsosubstreams';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $aStreams = array();
|
||||
private static array $aStreams = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aSubStreams;
|
||||
private array $aSubStreams;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iIndex;
|
||||
private int $iIndex;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sBuffer;
|
||||
private string $sBuffer;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bIsEnd;
|
||||
private bool $bIsEnd;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iPos;
|
||||
private int $iPos;
|
||||
|
||||
/**
|
||||
* @return resource|bool
|
||||
|
|
@ -69,20 +54,6 @@ class SubStreams
|
|||
return \fopen(self::STREAM_NAME.'://'.$sHashName, 'rb');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource|null
|
||||
*/
|
||||
protected function &getPart()
|
||||
{
|
||||
$nNull = null;
|
||||
if (isset($this->aSubStreams[$this->iIndex]))
|
||||
{
|
||||
return $this->aSubStreams[$this->iIndex];
|
||||
}
|
||||
|
||||
return $nNull;
|
||||
}
|
||||
|
||||
public function stream_open(string $sPath) : bool
|
||||
{
|
||||
$this->aSubStreams = array();
|
||||
|
|
@ -116,53 +87,41 @@ class SubStreams
|
|||
{
|
||||
$sReturn = '';
|
||||
$mCurrentPart = null;
|
||||
if ($iCount > 0)
|
||||
{
|
||||
if ($iCount < \strlen($this->sBuffer))
|
||||
{
|
||||
if ($iCount > 0) {
|
||||
if ($iCount < \strlen($this->sBuffer)) {
|
||||
$sReturn = \substr($this->sBuffer, 0, $iCount);
|
||||
$this->sBuffer = \substr($this->sBuffer, $iCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sReturn = $this->sBuffer;
|
||||
while ($iCount > 0)
|
||||
{
|
||||
$mCurrentPart =& $this->getPart();
|
||||
if (null === $mCurrentPart)
|
||||
{
|
||||
while ($iCount > 0) {
|
||||
$mCurrentPart = isset($this->aSubStreams[$this->iIndex])
|
||||
? $this->aSubStreams[$this->iIndex]
|
||||
: null;
|
||||
if (null === $mCurrentPart) {
|
||||
$this->bIsEnd = true;
|
||||
$this->sBuffer = '';
|
||||
$iCount = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (\is_resource($mCurrentPart))
|
||||
{
|
||||
if (!\feof($mCurrentPart))
|
||||
{
|
||||
if (\is_resource($mCurrentPart)) {
|
||||
if (!\feof($mCurrentPart)) {
|
||||
$sReadResult = \fread($mCurrentPart, 8192);
|
||||
if (false === $sReadResult)
|
||||
{
|
||||
if (false === $sReadResult) {
|
||||
return false;
|
||||
}
|
||||
$sReturn .= $sReadResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->iIndex++;
|
||||
} else {
|
||||
++$this->iIndex;
|
||||
}
|
||||
}
|
||||
|
||||
$iLen = \strlen($sReturn);
|
||||
if ($iCount < $iLen)
|
||||
{
|
||||
if ($iCount < $iLen) {
|
||||
$this->sBuffer = \substr($sReturn, $iCount);
|
||||
$sReturn = \substr($sReturn, 0, $iCount);
|
||||
$iCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$iCount -= $iLen;
|
||||
}
|
||||
}
|
||||
|
|
@ -211,6 +170,8 @@ class SubStreams
|
|||
|
||||
public function stream_seek() : bool
|
||||
{
|
||||
// $this->iPos = $offset;
|
||||
// foreach ($this->aSubStreams as $rStream) \fseek($rStream, $offset, $whence);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ namespace MailSo\Base\StreamWrappers;
|
|||
*/
|
||||
class TempFile
|
||||
{
|
||||
/** @var resource|null */
|
||||
public $context;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
|
@ -56,15 +59,11 @@ class TempFile
|
|||
self::STREAM_NAME === $aPath['scheme'])
|
||||
{
|
||||
$sHashName = $aPath['host'];
|
||||
if (isset(self::$aStreams[$sHashName]) &&
|
||||
\is_resource(self::$aStreams[$sHashName]))
|
||||
{
|
||||
if (isset(self::$aStreams[$sHashName]) && \is_resource(self::$aStreams[$sHashName])) {
|
||||
$this->rStream = self::$aStreams[$sHashName];
|
||||
\fseek($this->rStream, 0);
|
||||
$bResult = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$this->rStream = \fopen('php://temp', 'r+b');
|
||||
self::$aStreams[$sHashName] = $this->rStream;
|
||||
|
||||
|
|
|
|||
|
|
@ -190,8 +190,7 @@ abstract class Utils
|
|||
public static function FormatFileSize(int $iSize, int $iRound = 0) : string
|
||||
{
|
||||
$aSizes = array('B', 'KB', 'MB');
|
||||
for ($iIndex = 0; $iSize > 1024 && isset($aSizes[$iIndex + 1]); $iIndex++)
|
||||
{
|
||||
for ($iIndex = 0; $iSize > 1024 && isset($aSizes[$iIndex + 1]); ++$iIndex) {
|
||||
$iSize /= 1024;
|
||||
}
|
||||
return \round($iSize, $iRound).$aSizes[$iIndex];
|
||||
|
|
@ -223,12 +222,10 @@ abstract class Utils
|
|||
public static function DecodeHeaderValue(string $sEncodedValue, string $sIncomingCharset = '', string $sForcedIncomingCharset = '') : string
|
||||
{
|
||||
$sValue = $sEncodedValue;
|
||||
if (\strlen($sIncomingCharset))
|
||||
{
|
||||
if (\strlen($sIncomingCharset)) {
|
||||
$sIncomingCharset = static::NormalizeCharsetByValue($sIncomingCharset, $sValue);
|
||||
|
||||
$sValue = static::ConvertEncoding($sValue, $sIncomingCharset,
|
||||
Enumerations\Charset::UTF_8);
|
||||
$sValue = static::ConvertEncoding($sValue, $sIncomingCharset, Enumerations\Charset::UTF_8);
|
||||
}
|
||||
|
||||
$sValue = \preg_replace('/\?=[\n\r\t\s]{1,5}=\?/m', '?==?', $sValue);
|
||||
|
|
@ -239,15 +236,11 @@ abstract class Utils
|
|||
// \preg_match_all('/=\?[^\?]+\?[q|b|Q|B]\?[^\?]*?\?=/', $sValue, $aMatch);
|
||||
\preg_match_all('/=\?[^\?]+\?[q|b|Q|B]\?.*?\?=/', $sValue, $aMatch);
|
||||
|
||||
if (isset($aMatch[0]) && \is_array($aMatch[0]))
|
||||
{
|
||||
for ($iIndex = 0, $iLen = \count($aMatch[0]); $iIndex < $iLen; $iIndex++)
|
||||
{
|
||||
if (isset($aMatch[0][$iIndex]))
|
||||
{
|
||||
if (isset($aMatch[0]) && \is_array($aMatch[0])) {
|
||||
for ($iIndex = 0, $iLen = \count($aMatch[0]); $iIndex < $iLen; ++$iIndex) {
|
||||
if (isset($aMatch[0][$iIndex])) {
|
||||
$iPos = \strpos($aMatch[0][$iIndex], '*');
|
||||
if (false !== $iPos)
|
||||
{
|
||||
if (false !== $iPos) {
|
||||
$aMatch[0][$iIndex][0] = \substr($aMatch[0][$iIndex][0], 0, $iPos);
|
||||
}
|
||||
}
|
||||
|
|
@ -261,11 +254,9 @@ abstract class Utils
|
|||
$sMainCharset = '';
|
||||
$bOneCharset = true;
|
||||
|
||||
for ($iIndex = 0, $iLen = \count($aEncodeArray); $iIndex < $iLen; $iIndex++)
|
||||
{
|
||||
for ($iIndex = 0, $iLen = \count($aEncodeArray); $iIndex < $iLen; ++$iIndex) {
|
||||
$aTempArr = array('', $aEncodeArray[$iIndex]);
|
||||
if ('=?' === \substr(\trim($aTempArr[1]), 0, 2))
|
||||
{
|
||||
if ('=?' === \substr(\trim($aTempArr[1]), 0, 2)) {
|
||||
$iPos = \strpos($aTempArr[1], '?', 2);
|
||||
$aTempArr[0] = \substr($aTempArr[1], 2, $iPos - 2);
|
||||
$sEncType = \strtoupper($aTempArr[1][$iPos + 1]);
|
||||
|
|
@ -284,17 +275,13 @@ abstract class Utils
|
|||
}
|
||||
}
|
||||
|
||||
if (\strlen($aTempArr[0]))
|
||||
{
|
||||
if (\strlen($aTempArr[0])) {
|
||||
$sCharset = \strlen($sForcedIncomingCharset) ? $sForcedIncomingCharset : $aTempArr[0];
|
||||
$sCharset = static::NormalizeCharset($sCharset, true);
|
||||
|
||||
if ('' === $sMainCharset)
|
||||
{
|
||||
if ('' === $sMainCharset) {
|
||||
$sMainCharset = $sCharset;
|
||||
}
|
||||
else if ($sMainCharset !== $sCharset)
|
||||
{
|
||||
} else if ($sMainCharset !== $sCharset) {
|
||||
$bOneCharset = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -308,14 +295,10 @@ abstract class Utils
|
|||
unset($aTempArr);
|
||||
}
|
||||
|
||||
for ($iIndex = 0, $iLen = \count($aParts); $iIndex < $iLen; $iIndex++)
|
||||
{
|
||||
if ($bOneCharset)
|
||||
{
|
||||
for ($iIndex = 0, $iLen = \count($aParts); $iIndex < $iLen; ++$iIndex) {
|
||||
if ($bOneCharset) {
|
||||
$sValue = \str_replace($aParts[$iIndex][0], $aParts[$iIndex][1], $sValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$aParts[$iIndex][2] = static::NormalizeCharsetByValue($aParts[$iIndex][2], $aParts[$iIndex][1]);
|
||||
|
||||
$sValue = \str_replace($aParts[$iIndex][0],
|
||||
|
|
@ -324,8 +307,7 @@ abstract class Utils
|
|||
}
|
||||
}
|
||||
|
||||
if ($bOneCharset && \strlen($sMainCharset))
|
||||
{
|
||||
if ($bOneCharset && \strlen($sMainCharset)) {
|
||||
$sMainCharset = static::NormalizeCharsetByValue($sMainCharset, $sValue);
|
||||
$sValue = static::ConvertEncoding($sValue, $sMainCharset, Enumerations\Charset::UTF_8);
|
||||
}
|
||||
|
|
@ -337,8 +319,7 @@ abstract class Utils
|
|||
{
|
||||
$sResultHeaders = $sIncHeaders;
|
||||
|
||||
if ($aHeadersToRemove)
|
||||
{
|
||||
if ($aHeadersToRemove) {
|
||||
$aHeadersToRemove = \array_map('strtolower', $aHeadersToRemove);
|
||||
|
||||
$sIncHeaders = \preg_replace('/[\r\n]+/', "\n", $sIncHeaders);
|
||||
|
|
@ -347,31 +328,21 @@ abstract class Utils
|
|||
$bSkip = false;
|
||||
$aResult = array();
|
||||
|
||||
foreach ($aHeaders as $sLine)
|
||||
{
|
||||
if (\strlen($sLine))
|
||||
{
|
||||
foreach ($aHeaders as $sLine) {
|
||||
if (\strlen($sLine)) {
|
||||
$sFirst = \substr($sLine,0,1);
|
||||
if (' ' === $sFirst || "\t" === $sFirst)
|
||||
{
|
||||
if (!$bSkip)
|
||||
{
|
||||
if (' ' === $sFirst || "\t" === $sFirst) {
|
||||
if (!$bSkip) {
|
||||
$aResult[] = $sLine;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$bSkip = false;
|
||||
$aParts = \explode(':', $sLine, 2);
|
||||
|
||||
if (!empty($aParts) && !empty($aParts[0]))
|
||||
{
|
||||
if (\in_array(\strtolower(\trim($aParts[0])), $aHeadersToRemove))
|
||||
{
|
||||
if (!empty($aParts) && !empty($aParts[0])) {
|
||||
if (\in_array(\strtolower(\trim($aParts[0])), $aHeadersToRemove)) {
|
||||
$bSkip = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$aResult[] = $sLine;
|
||||
}
|
||||
}
|
||||
|
|
@ -388,20 +359,17 @@ abstract class Utils
|
|||
public static function EncodeUnencodedValue(string $sEncodeType, string $sValue) : string
|
||||
{
|
||||
$sValue = \trim($sValue);
|
||||
if (\strlen($sValue) && !static::IsAscii($sValue))
|
||||
{
|
||||
if (\strlen($sValue) && !static::IsAscii($sValue)) {
|
||||
switch (\strtoupper($sEncodeType))
|
||||
{
|
||||
case 'B':
|
||||
$sValue = '=?'.\strtolower(Enumerations\Charset::UTF_8).
|
||||
return '=?'.\strtolower(Enumerations\Charset::UTF_8).
|
||||
'?B?'.\base64_encode($sValue).'?=';
|
||||
break;
|
||||
|
||||
case 'Q':
|
||||
$sValue = '=?'.\strtolower(Enumerations\Charset::UTF_8).
|
||||
return '=?'.\strtolower(Enumerations\Charset::UTF_8).
|
||||
'?Q?'.\str_replace(array('?', ' ', '_'), array('=3F', '_', '=5F'),
|
||||
\quoted_printable_encode($sValue)).'?=';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -418,28 +386,22 @@ abstract class Utils
|
|||
$iNlen = \strlen($sAttrName);
|
||||
$iVlen = \strlen($sValue);
|
||||
|
||||
if (\strlen($sAttrName) + $iVlen > $iLen - 3)
|
||||
{
|
||||
if (\strlen($sAttrName) + $iVlen > $iLen - 3) {
|
||||
$sections = array();
|
||||
$section = 0;
|
||||
|
||||
for ($i = 0, $j = 0; $i < $iVlen; $i += $j)
|
||||
{
|
||||
for ($i = 0, $j = 0; $i < $iVlen; $i += $j) {
|
||||
$j = $iLen - $iNlen - \strlen($section) - 4;
|
||||
$sections[$section++] = \substr($sValue, $i, $j);
|
||||
}
|
||||
|
||||
for ($i = 0, $n = $section; $i < $n; $i++)
|
||||
{
|
||||
for ($i = 0, $n = $section; $i < $n; ++$i) {
|
||||
$sections[$i] = ' '.$sAttrName.'*'.$i.'*='.$sections[$i];
|
||||
}
|
||||
|
||||
return \implode(";\r\n", $sections);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $sAttrName.'*='.$sValue;
|
||||
}
|
||||
return $sAttrName.'*='.$sValue;
|
||||
}
|
||||
|
||||
public static function EncodeHeaderUtf8AttributeValue(string $sAttrName, string $sValue) : string
|
||||
|
|
@ -447,12 +409,9 @@ abstract class Utils
|
|||
$sAttrName = \trim($sAttrName);
|
||||
$sValue = \trim($sValue);
|
||||
|
||||
if (\strlen($sValue) && !static::IsAscii($sValue))
|
||||
{
|
||||
if (\strlen($sValue) && !static::IsAscii($sValue)) {
|
||||
$sValue = static::AttributeRfc2231Encode($sAttrName, $sValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sValue = $sAttrName.'="'.\str_replace('"', '\\"', $sValue).'"';
|
||||
}
|
||||
|
||||
|
|
@ -462,8 +421,7 @@ abstract class Utils
|
|||
public static function GetAccountNameFromEmail(string $sEmail) : string
|
||||
{
|
||||
$sResult = '';
|
||||
if (\strlen($sEmail))
|
||||
{
|
||||
if (\strlen($sEmail)) {
|
||||
$iPos = \strrpos($sEmail, '@');
|
||||
$sResult = (false === $iPos) ? $sEmail : \substr($sEmail, 0, $iPos);
|
||||
}
|
||||
|
|
@ -474,11 +432,9 @@ abstract class Utils
|
|||
public static function GetDomainFromEmail(string $sEmail) : string
|
||||
{
|
||||
$sResult = '';
|
||||
if (\strlen($sEmail))
|
||||
{
|
||||
if (\strlen($sEmail)) {
|
||||
$iPos = \strrpos($sEmail, '@');
|
||||
if (false !== $iPos && 0 < $iPos)
|
||||
{
|
||||
if (false !== $iPos && 0 < $iPos) {
|
||||
$sResult = \substr($sEmail, $iPos + 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -501,243 +457,13 @@ abstract class Utils
|
|||
return false === $iLast ? '' : \strtolower(\substr($sFileName, $iLast + 1));
|
||||
}
|
||||
|
||||
public static function MimeContentType(string $sFileName) : string
|
||||
{
|
||||
$sResult = 'application/octet-stream';
|
||||
$sFileName = \trim(\strtolower($sFileName));
|
||||
|
||||
if ('winmail.dat' === $sFileName)
|
||||
{
|
||||
return 'application/ms-tnef';
|
||||
}
|
||||
|
||||
$aMimeTypes = array(
|
||||
|
||||
'eml' => 'message/rfc822',
|
||||
'mime' => 'message/rfc822',
|
||||
'txt' => 'text/plain',
|
||||
'text' => 'text/plain',
|
||||
'def' => 'text/plain',
|
||||
'list' => 'text/plain',
|
||||
'in' => 'text/plain',
|
||||
'ini' => 'text/plain',
|
||||
'log' => 'text/plain',
|
||||
'sql' => 'text/plain',
|
||||
'cfg' => 'text/plain',
|
||||
'conf' => 'text/plain',
|
||||
'rtx' => 'text/richtext',
|
||||
'vcard' => 'text/vcard',
|
||||
'vcf' => 'text/vcard',
|
||||
'htm' => 'text/html',
|
||||
'html' => 'text/html',
|
||||
'csv' => 'text/csv',
|
||||
'ics' => 'text/calendar',
|
||||
'ifb' => 'text/calendar',
|
||||
'xml' => 'text/xml',
|
||||
'json' => 'application/json',
|
||||
'swf' => 'application/x-shockwave-flash',
|
||||
'hlp' => 'application/winhlp',
|
||||
'wgt' => 'application/widget',
|
||||
'chm' => 'application/vnd.ms-htmlhelp',
|
||||
'asc' => 'application/pgp-signature',
|
||||
'p10' => 'application/pkcs10',
|
||||
'p7c' => 'application/pkcs7-mime',
|
||||
'p7m' => 'application/pkcs7-mime',
|
||||
'p7s' => 'application/pkcs7-signature',
|
||||
'torrent' => 'application/x-bittorrent',
|
||||
|
||||
// scripts
|
||||
'js' => 'application/javascript',
|
||||
'pl' => 'text/perl',
|
||||
'css' => 'text/css',
|
||||
'asp' => 'text/asp',
|
||||
'php' => 'application/x-php',
|
||||
|
||||
// images
|
||||
'png' => 'image/png',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'jpe' => 'image/jpeg',
|
||||
'jfif' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'bmp' => 'image/bmp',
|
||||
'cgm' => 'image/cgm',
|
||||
'ief' => 'image/ief',
|
||||
'ico' => 'image/x-icon',
|
||||
'tif' => 'image/tiff',
|
||||
'tiff' => 'image/tiff',
|
||||
'svg' => 'image/svg+xml',
|
||||
'svgz' => 'image/svg+xml',
|
||||
'djv' => 'image/vnd.djvu',
|
||||
'djvu' => 'image/vnd.djvu',
|
||||
'webp' => 'image/webp',
|
||||
|
||||
// archives
|
||||
'zip' => 'application/zip',
|
||||
'7z' => 'application/x-7z-compressed',
|
||||
'rar' => 'application/x-rar-compressed',
|
||||
'exe' => 'application/x-msdownload',
|
||||
'dll' => 'application/x-msdownload',
|
||||
'scr' => 'application/x-msdownload',
|
||||
'com' => 'application/x-msdownload',
|
||||
'bat' => 'application/x-msdownload',
|
||||
'msi' => 'application/x-msdownload',
|
||||
'cab' => 'application/vnd.ms-cab-compressed',
|
||||
'gz' => 'application/x-gzip',
|
||||
'tgz' => 'application/x-gzip',
|
||||
'bz' => 'application/x-bzip',
|
||||
'bz2' => 'application/x-bzip2',
|
||||
'deb' => 'application/x-debian-package',
|
||||
'tar' => 'application/x-tar',
|
||||
|
||||
// fonts
|
||||
'psf' => 'application/x-font-linux-psf',
|
||||
'otf' => 'application/x-font-otf',
|
||||
'pcf' => 'application/x-font-pcf',
|
||||
'snf' => 'application/x-font-snf',
|
||||
'ttf' => 'application/x-font-ttf',
|
||||
'ttc' => 'application/x-font-ttf',
|
||||
|
||||
// audio
|
||||
'aac' => 'audio/aac',
|
||||
'flac' => 'audio/flac',
|
||||
'mp3' => 'audio/mpeg',
|
||||
'aif' => 'audio/aiff',
|
||||
'aifc' => 'audio/aiff',
|
||||
'aiff' => 'audio/aiff',
|
||||
'wav' => 'audio/x-wav',
|
||||
'midi' => 'audio/midi',
|
||||
'mp4a' => 'audio/mp4',
|
||||
'ogg' => 'audio/ogg',
|
||||
'weba' => 'audio/webm',
|
||||
'm3u' => 'audio/x-mpegurl',
|
||||
|
||||
// video
|
||||
'qt' => 'video/quicktime',
|
||||
'mov' => 'video/quicktime',
|
||||
'avi' => 'video/x-msvideo',
|
||||
'mpg' => 'video/mpeg',
|
||||
'mpeg' => 'video/mpeg',
|
||||
'mpe' => 'video/mpeg',
|
||||
'm1v' => 'video/mpeg',
|
||||
'm2v' => 'video/mpeg',
|
||||
'3gp' => 'video/3gpp',
|
||||
'3g2' => 'video/3gpp2',
|
||||
'h261' => 'video/h261',
|
||||
'h263' => 'video/h263',
|
||||
'h264' => 'video/h264',
|
||||
'jpgv' => 'video/jpgv',
|
||||
'mp4' => 'video/mp4',
|
||||
'mp4v' => 'video/mp4',
|
||||
'mpg4' => 'video/mp4',
|
||||
'ogv' => 'video/ogg',
|
||||
'webm' => 'video/webm',
|
||||
'm4v' => 'video/x-m4v',
|
||||
'asf' => 'video/x-ms-asf',
|
||||
'asx' => 'video/x-ms-asf',
|
||||
'wm' => 'video/x-ms-wm',
|
||||
'wmv' => 'video/x-ms-wmv',
|
||||
'wmx' => 'video/x-ms-wmx',
|
||||
'wvx' => 'video/x-ms-wvx',
|
||||
'movie' => 'video/x-sgi-movie',
|
||||
|
||||
// adobe
|
||||
'pdf' => 'application/pdf',
|
||||
'psd' => 'image/vnd.adobe.photoshop',
|
||||
'ai' => 'application/postscript',
|
||||
'eps' => 'application/postscript',
|
||||
'ps' => 'application/postscript',
|
||||
|
||||
// ms office
|
||||
'doc' => 'application/msword',
|
||||
'dot' => 'application/msword',
|
||||
'rtf' => 'application/rtf',
|
||||
'xls' => 'application/vnd.ms-excel',
|
||||
'ppt' => 'application/vnd.ms-powerpoint',
|
||||
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
|
||||
// open office
|
||||
'odt' => 'application/vnd.oasis.opendocument.text',
|
||||
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'odp' => 'application/vnd.oasis.opendocument.presentation'
|
||||
|
||||
);
|
||||
|
||||
$sExt = static::GetFileExtension($sFileName);
|
||||
if (\strlen($sExt) && isset($aMimeTypes[$sExt]))
|
||||
{
|
||||
$sResult = $aMimeTypes[$sExt];
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
public static function ContentTypeType(string $sContentType, string $sFileName) : string
|
||||
{
|
||||
$sContentType = \strtolower($sContentType);
|
||||
if (\str_starts_with($sContentType, 'image/')) {
|
||||
return 'image';
|
||||
}
|
||||
|
||||
switch ($sContentType)
|
||||
{
|
||||
case 'application/zip':
|
||||
case 'application/x-7z-compressed':
|
||||
case 'application/x-rar-compressed':
|
||||
case 'application/x-msdownload':
|
||||
case 'application/vnd.ms-cab-compressed':
|
||||
case 'application/gzip':
|
||||
case 'application/x-gzip':
|
||||
case 'application/x-bzip':
|
||||
case 'application/x-bzip2':
|
||||
case 'application/x-debian-package':
|
||||
case 'application/x-tar':
|
||||
return 'archive';
|
||||
|
||||
case 'application/msword':
|
||||
case 'application/rtf':
|
||||
case 'application/vnd.ms-excel':
|
||||
case 'application/vnd.ms-powerpoint':
|
||||
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
||||
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
||||
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.template':
|
||||
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
||||
case 'application/vnd.oasis.opendocument.text':
|
||||
case 'application/vnd.oasis.opendocument.spreadsheet':
|
||||
return 'doc';
|
||||
|
||||
case 'application/pdf':
|
||||
case 'application/x-pdf':
|
||||
return 'pdf';
|
||||
}
|
||||
|
||||
switch (\strtolower(static::GetFileExtension($sFileName)))
|
||||
{
|
||||
case 'zip':
|
||||
case '7z':
|
||||
case 'rar':
|
||||
case 'tar':
|
||||
case 'tgz':
|
||||
return 'archive';
|
||||
|
||||
case 'pdf':
|
||||
return 'pdf';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @staticvar bool $bValidateAction
|
||||
*/
|
||||
public static function ResetTimeLimit(int $iTimeToReset = 15, int $iTimeToAdd = 120) : bool
|
||||
{
|
||||
$iTime = \time();
|
||||
if ($iTime < $_SERVER['REQUEST_TIME_FLOAT'] + 5)
|
||||
{
|
||||
if ($iTime < $_SERVER['REQUEST_TIME_FLOAT'] + 5) {
|
||||
// do nothing first 5s
|
||||
return true;
|
||||
}
|
||||
|
|
@ -745,18 +471,15 @@ abstract class Utils
|
|||
static $bValidateAction = null;
|
||||
static $iResetTimer = null;
|
||||
|
||||
if (null === $bValidateAction)
|
||||
{
|
||||
if (null === $bValidateAction) {
|
||||
$iResetTimer = 0;
|
||||
|
||||
$bValidateAction = static::FunctionCallable('set_time_limit');
|
||||
}
|
||||
|
||||
if ($bValidateAction && $iTimeToReset < $iTime - $iResetTimer)
|
||||
{
|
||||
if ($bValidateAction && $iTimeToReset < $iTime - $iResetTimer) {
|
||||
$iResetTimer = $iTime;
|
||||
if (!\set_time_limit($iTimeToAdd))
|
||||
{
|
||||
if (!\set_time_limit($iTimeToAdd)) {
|
||||
$bValidateAction = false;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -767,11 +490,13 @@ abstract class Utils
|
|||
return false;
|
||||
}
|
||||
|
||||
# Replace ampersand, spaces and reserved characters (based on Win95 VFAT)
|
||||
# en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
||||
/**
|
||||
* Replace control characters, ampersand and reserved characters (based on Win95 VFAT)
|
||||
* en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
|
||||
*/
|
||||
public static function SecureFileName(string $sFileName) : string
|
||||
{
|
||||
return \preg_replace('#[|\\\\?*<":>+\\[\\]/&\\s\\pC]#su', '-', $sFileName);
|
||||
return \preg_replace('#[|\\\\?*<":>+\\[\\]/&\\pC]#su', '-', $sFileName);
|
||||
}
|
||||
|
||||
public static function Trim(string $sValue) : string
|
||||
|
|
@ -781,24 +506,7 @@ abstract class Utils
|
|||
|
||||
public static function RecRmDir(string $sDir) : bool
|
||||
{
|
||||
\clearstatcache();
|
||||
if (\is_dir($sDir)) {
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($sDir, \FilesystemIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST);
|
||||
foreach ($iterator as $path) {
|
||||
if ($path->isDir()) {
|
||||
\rmdir($path);
|
||||
} else {
|
||||
\unlink($path);
|
||||
}
|
||||
}
|
||||
\clearstatcache();
|
||||
// \realpath_cache_size() && \clearstatcache(true);
|
||||
return \rmdir($sDir);
|
||||
}
|
||||
|
||||
return false;
|
||||
return static::RecTimeDirRemove($sDir, 0);
|
||||
}
|
||||
|
||||
public static function RecTimeDirRemove(string $sDir, int $iTime2Kill) : bool
|
||||
|
|
@ -810,9 +518,10 @@ abstract class Utils
|
|||
new \RecursiveDirectoryIterator($sDir, \FilesystemIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST);
|
||||
foreach ($iterator as $path) {
|
||||
if ($path->isFile() && $path->getMTime() < $iTime) {
|
||||
if ($path->isFile() && (!$iTime2Kill || $path->getMTime() < $iTime)) {
|
||||
\is_callable('opcache_invalidate') && \opcache_invalidate($path, true);
|
||||
\unlink($path);
|
||||
} else if ($path->isDir() && !(new \FilesystemIterator($path))->valid()) {
|
||||
} else if ($path->isDir() && (!$iTime2Kill || !(new \FilesystemIterator($path))->valid())) {
|
||||
\rmdir($path);
|
||||
}
|
||||
}
|
||||
|
|
@ -848,24 +557,19 @@ abstract class Utils
|
|||
public static function Base64Decode(string $sString) : string
|
||||
{
|
||||
$sResultString = \base64_decode($sString, true);
|
||||
if (false === $sResultString)
|
||||
{
|
||||
if (false === $sResultString) {
|
||||
$sString = \str_replace(array(' ', "\r", "\n", "\t"), '', $sString);
|
||||
$sString = \preg_replace('/[^a-zA-Z0-9=+\/](.*)$/', '', $sString);
|
||||
|
||||
if (false !== \strpos(\trim(\trim($sString), '='), '='))
|
||||
{
|
||||
if (false !== \strpos(\trim(\trim($sString), '='), '=')) {
|
||||
$sString = \preg_replace('/=([^=])/', '= $1', $sString);
|
||||
$aStrings = \explode(' ', $sString);
|
||||
foreach ($aStrings as $iIndex => $sParts)
|
||||
{
|
||||
foreach ($aStrings as $iIndex => $sParts) {
|
||||
$aStrings[$iIndex] = \base64_decode($sParts);
|
||||
}
|
||||
|
||||
$sResultString = \implode('', $aStrings);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sResultString = \base64_decode($sString);
|
||||
}
|
||||
}
|
||||
|
|
@ -880,7 +584,7 @@ abstract class Utils
|
|||
|
||||
public static function UrlSafeBase64Decode(string $sValue) : string
|
||||
{
|
||||
return \base64_decode(\strtr($sValue, '-_', '+/'), '=');
|
||||
return \base64_decode(\strtr($sValue, '-_', '+/'), true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -888,23 +592,16 @@ abstract class Utils
|
|||
*/
|
||||
public static function FpassthruWithTimeLimitReset($fResource, int $iBufferLen = 8192) : bool
|
||||
{
|
||||
$bResult = false;
|
||||
if (\is_resource($fResource))
|
||||
{
|
||||
while (!\feof($fResource))
|
||||
{
|
||||
$bResult = \is_resource($fResource);
|
||||
if ($bResult) {
|
||||
while (!\feof($fResource)) {
|
||||
$sBuffer = \fread($fResource, $iBufferLen);
|
||||
if (false !== $sBuffer)
|
||||
{
|
||||
echo $sBuffer;
|
||||
static::ResetTimeLimit();
|
||||
continue;
|
||||
if (false === $sBuffer) {
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
echo $sBuffer;
|
||||
static::ResetTimeLimit();
|
||||
}
|
||||
|
||||
$bResult = true;
|
||||
}
|
||||
|
||||
return $bResult;
|
||||
|
|
@ -912,63 +609,44 @@ abstract class Utils
|
|||
|
||||
/**
|
||||
* @param resource $rRead
|
||||
* @param resource $rWrite
|
||||
*/
|
||||
public static function MultipleStreamWriter($rRead, array $aWrite, int $iBufferLen = 8192, bool $bResetTimeLimit = true, bool $bFixCrLf = false, bool $bRewindOnComplete = false) : int
|
||||
public static function WriteStream($rRead, $rWrite, int $iBufferLen = 8192, bool $bFixCrLf = false, bool $bRewindOnComplete = false) : int
|
||||
{
|
||||
$mResult = false;
|
||||
if (\is_resource($rRead) && \count($aWrite))
|
||||
{
|
||||
$mResult = 0;
|
||||
while (!\feof($rRead))
|
||||
{
|
||||
$sBuffer = \fread($rRead, $iBufferLen);
|
||||
if (false === $sBuffer)
|
||||
{
|
||||
$mResult = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if ('' === $sBuffer)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if ($bFixCrLf)
|
||||
{
|
||||
$sBuffer = \str_replace("\n", "\r\n", \str_replace("\r", '', $sBuffer));
|
||||
}
|
||||
|
||||
$mResult += \strlen($sBuffer);
|
||||
|
||||
foreach ($aWrite as $rWriteStream)
|
||||
{
|
||||
$mWriteResult = \fwrite($rWriteStream, $sBuffer);
|
||||
if (false === $mWriteResult)
|
||||
{
|
||||
$mResult = false;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bResetTimeLimit)
|
||||
{
|
||||
static::ResetTimeLimit();
|
||||
}
|
||||
}
|
||||
if (!\is_resource($rRead) || !\is_resource($rWrite)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ($mResult && $bRewindOnComplete)
|
||||
{
|
||||
foreach ($aWrite as $rWriteStream)
|
||||
{
|
||||
if (\is_resource($rWriteStream))
|
||||
{
|
||||
\rewind($rWriteStream);
|
||||
}
|
||||
$iResult = 0;
|
||||
|
||||
while (!\feof($rRead)) {
|
||||
$sBuffer = \fread($rRead, $iBufferLen);
|
||||
if (false === $sBuffer) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ('' === $sBuffer) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($bFixCrLf) {
|
||||
$sBuffer = \str_replace("\n", "\r\n", \str_replace("\r", '', $sBuffer));
|
||||
}
|
||||
|
||||
$iResult += \strlen($sBuffer);
|
||||
|
||||
if (false === \fwrite($rWrite, $sBuffer)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static::ResetTimeLimit();
|
||||
}
|
||||
|
||||
return $mResult;
|
||||
if ($bRewindOnComplete) {
|
||||
\rewind($rWrite);
|
||||
}
|
||||
|
||||
return $iResult;
|
||||
}
|
||||
|
||||
public static function Utf7ModifiedToUtf8(string $sStr) : string
|
||||
|
|
@ -1016,8 +694,7 @@ abstract class Utils
|
|||
public static function ValidateDomain(string $sDomain, bool $bSimple = false) : bool
|
||||
{
|
||||
$aMatch = array();
|
||||
if ($bSimple)
|
||||
{
|
||||
if ($bSimple) {
|
||||
return \preg_match('/.+(\.[a-zA-Z]+)$/', $sDomain, $aMatch) && !empty($aMatch[1]);
|
||||
}
|
||||
|
||||
|
|
@ -1034,8 +711,7 @@ abstract class Utils
|
|||
|
||||
public static function IdnToUtf8(string $sStr, bool $bLowerIfAscii = false) : string
|
||||
{
|
||||
if (\strlen($sStr) && \preg_match('/(^|\.|@)xn--/i', $sStr))
|
||||
{
|
||||
if (\strlen($sStr) && \preg_match('/(^|\.|@)xn--/i', $sStr)) {
|
||||
try
|
||||
{
|
||||
$sStr = \SnappyMail\IDN::anyToUtf8($sStr);
|
||||
|
|
@ -1052,14 +728,12 @@ abstract class Utils
|
|||
|
||||
$sUser = '';
|
||||
$sDomain = $sStr;
|
||||
if (false !== \strpos($sStr, '@'))
|
||||
{
|
||||
if (false !== \strpos($sStr, '@')) {
|
||||
$sUser = static::GetAccountNameFromEmail($sStr);
|
||||
$sDomain = static::GetDomainFromEmail($sStr);
|
||||
}
|
||||
|
||||
if (\strlen($sDomain) && \preg_match('/[^\x20-\x7E]/', $sDomain))
|
||||
{
|
||||
if (\strlen($sDomain) && \preg_match('/[^\x20-\x7E]/', $sDomain)) {
|
||||
try
|
||||
{
|
||||
$sDomain = \SnappyMail\IDN::anyToAscii($sDomain);
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2014 Usenko Timur
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo\Base;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Base
|
||||
*/
|
||||
class Validator
|
||||
{
|
||||
public static function RangeInt(int $iNumber, int $iMin = null, int $iMax = null) : bool
|
||||
{
|
||||
return (null === $iMin || $iNumber >= $iMin) && (null === $iMax || $iNumber <= $iMax);
|
||||
}
|
||||
|
||||
public static function PortInt(int $iPort) : bool
|
||||
{
|
||||
return static::RangeInt($iPort, 0, 65535);
|
||||
}
|
||||
}
|
||||
|
|
@ -22,10 +22,8 @@ class Xxtea
|
|||
{
|
||||
$aV = self::str2long($sString, true);
|
||||
$aK = self::str2long($sKey, false);
|
||||
if (\count($aK) < 4)
|
||||
{
|
||||
for ($iIndex = \count($aK); $iIndex < 4; $iIndex++)
|
||||
{
|
||||
if (\count($aK) < 4) {
|
||||
for ($iIndex = \count($aK); $iIndex < 4; $iIndex++) {
|
||||
$aK[$iIndex] = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -36,12 +34,10 @@ class Xxtea
|
|||
$iDelta = 0x9E3779B9;
|
||||
$iQ = \floor(6 + 52 / ($iN + 1));
|
||||
$iSum = 0;
|
||||
while (0 < $iQ--)
|
||||
{
|
||||
while (0 < $iQ--) {
|
||||
$iSum = self::int32($iSum + $iDelta);
|
||||
$iE = $iSum >> 2 & 3;
|
||||
for ($iPIndex = 0; $iPIndex < $iN; $iPIndex++)
|
||||
{
|
||||
for ($iPIndex = 0; $iPIndex < $iN; ++$iPIndex) {
|
||||
$iY = $aV[$iPIndex + 1];
|
||||
$iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) +
|
||||
(($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ));
|
||||
|
|
@ -61,10 +57,8 @@ class Xxtea
|
|||
$aV = self::str2long($sEncryptedString, false);
|
||||
$aK = self::str2long($sKey, false);
|
||||
|
||||
if (\count($aK) < 4)
|
||||
{
|
||||
for ($iIndex = \count($aK); $iIndex < 4; $iIndex++)
|
||||
{
|
||||
if (\count($aK) < 4) {
|
||||
for ($iIndex = \count($aK); $iIndex < 4; ++$iIndex) {
|
||||
$aK[$iIndex] = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -76,11 +70,9 @@ class Xxtea
|
|||
$iDelta = 0x9E3779B9;
|
||||
$iQ = \floor(6 + 52 / ($iN + 1));
|
||||
$iSum = self::int32($iQ * $iDelta);
|
||||
while ($iSum != 0)
|
||||
{
|
||||
while ($iSum != 0) {
|
||||
$iE = $iSum >> 2 & 3;
|
||||
for ($iPIndex = $iN; $iPIndex > 0; $iPIndex--)
|
||||
{
|
||||
for ($iPIndex = $iN; $iPIndex > 0; --$iPIndex) {
|
||||
$iZ = $aV[$iPIndex - 1];
|
||||
$iMx = self::int32((($iZ >> 5 & 0x07ffffff) ^ $iY << 2) +
|
||||
(($iY >> 3 & 0x1fffffff) ^ $iZ << 4)) ^ self::int32(($iSum ^ $iY) + ($aK[$iPIndex & 3 ^ $iE] ^ $iZ));
|
||||
|
|
@ -100,33 +92,25 @@ class Xxtea
|
|||
{
|
||||
$iLen = \count($aV);
|
||||
$iN = ($iLen - 1) << 2;
|
||||
if ($aW)
|
||||
{
|
||||
if ($aW) {
|
||||
$iM = $aV[$iLen - 1];
|
||||
if (($iM < $iN - 3) || ($iM > $iN))
|
||||
{
|
||||
if (($iM < $iN - 3) || ($iM > $iN)) {
|
||||
return false;
|
||||
}
|
||||
$iN = $iM;
|
||||
}
|
||||
$aS = array();
|
||||
for ($iIndex = 0; $iIndex < $iLen; $iIndex++)
|
||||
{
|
||||
for ($iIndex = 0; $iIndex < $iLen; ++$iIndex) {
|
||||
$aS[$iIndex] = \pack('V', $aV[$iIndex]);
|
||||
}
|
||||
if ($aW)
|
||||
{
|
||||
return \substr(\join('', $aS), 0, $iN);
|
||||
}
|
||||
return \join('', $aS);
|
||||
return $aW ? \substr(\join('', $aS), 0, $iN) : \join('', $aS);
|
||||
}
|
||||
|
||||
private static function str2long(string $sS, string $sW) : array
|
||||
{
|
||||
$aV = \unpack('V*', $sS . \str_repeat("\0", (4 - \strlen($sS) % 4) & 3));
|
||||
$aV = \array_values($aV);
|
||||
if ($sW)
|
||||
{
|
||||
if ($sW) {
|
||||
$aV[\count($aV)] = \strlen($sS);
|
||||
}
|
||||
return $aV;
|
||||
|
|
|
|||
|
|
@ -22,10 +22,7 @@ class CacheClient
|
|||
*/
|
||||
private $oDriver;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sCacheIndex = '';
|
||||
private string $sCacheIndex = '';
|
||||
|
||||
public function Set(string $sKey, string $sValue) : bool
|
||||
{
|
||||
|
|
@ -56,13 +53,11 @@ class CacheClient
|
|||
{
|
||||
$sValue = '';
|
||||
|
||||
if ($this->oDriver)
|
||||
{
|
||||
if ($this->oDriver) {
|
||||
$sValue = $this->oDriver->Get($sKey.$this->sCacheIndex);
|
||||
}
|
||||
|
||||
if ($bClearAfterGet)
|
||||
{
|
||||
if ($bClearAfterGet) {
|
||||
$this->Delete($sKey);
|
||||
}
|
||||
|
||||
|
|
@ -73,8 +68,7 @@ class CacheClient
|
|||
{
|
||||
$iTimer = 0;
|
||||
$sValue = $this->Get($sKey.'/TIMER');
|
||||
if (\strlen($sValue) && is_numeric($sValue))
|
||||
{
|
||||
if (\strlen($sValue) && \is_numeric($sValue)) {
|
||||
$iTimer = (int) $sValue;
|
||||
}
|
||||
|
||||
|
|
@ -83,8 +77,7 @@ class CacheClient
|
|||
|
||||
public function Delete(string $sKey) : self
|
||||
{
|
||||
if ($this->oDriver)
|
||||
{
|
||||
if ($this->oDriver) {
|
||||
$this->oDriver->Delete($sKey.$this->sCacheIndex);
|
||||
}
|
||||
|
||||
|
|
@ -117,11 +110,9 @@ class CacheClient
|
|||
|
||||
public function Verify(bool $bCache = false) : bool
|
||||
{
|
||||
if ($this->oDriver)
|
||||
{
|
||||
if ($this->oDriver) {
|
||||
$sCacheData = \gmdate('Y-m-d-H');
|
||||
if ($bCache && $sCacheData === $this->Get('__verify_key__'))
|
||||
{
|
||||
if ($bCache && $sCacheData === $this->Get('__verify_key__')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,19 +18,13 @@ namespace MailSo\Cache\Drivers;
|
|||
*/
|
||||
class APCU implements \MailSo\Cache\DriverInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sKeyPrefix;
|
||||
private string $sKeyPrefix;
|
||||
|
||||
function __construct(string $sKeyPrefix = '')
|
||||
{
|
||||
$this->sKeyPrefix = $sKeyPrefix;
|
||||
if (!empty($this->sKeyPrefix))
|
||||
{
|
||||
$this->sKeyPrefix =
|
||||
\preg_replace('/[^a-zA-Z0-9_]/', '_', rtrim(trim($this->sKeyPrefix), '\\/')).'/';
|
||||
}
|
||||
$this->sKeyPrefix = empty($sKeyPrefix)
|
||||
? $sKeyPrefix
|
||||
: \preg_replace('/[^a-zA-Z0-9_]/', '_', \rtrim(\trim($sKeyPrefix), '\\/')).'/';
|
||||
}
|
||||
|
||||
public function Set(string $sKey, string $sValue) : bool
|
||||
|
|
@ -51,12 +45,7 @@ class APCU implements \MailSo\Cache\DriverInterface
|
|||
|
||||
public function GC(int $iTimeToClearInHours = 24) : bool
|
||||
{
|
||||
if (0 === $iTimeToClearInHours)
|
||||
{
|
||||
return \apcu_clear_cache('user');
|
||||
}
|
||||
|
||||
return false;
|
||||
return (0 === $iTimeToClearInHours) ? \apcu_clear_cache('user') : false;
|
||||
}
|
||||
|
||||
private function generateCachedKey(string $sKey) : string
|
||||
|
|
|
|||
|
|
@ -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.'/';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -55,49 +45,41 @@ class File implements \MailSo\Cache\DriverInterface
|
|||
{
|
||||
$sValue = '';
|
||||
$sPath = $this->generateCachedFileName($sKey);
|
||||
if ('' !== $sPath && \file_exists($sPath))
|
||||
{
|
||||
if ('' !== $sPath && \file_exists($sPath)) {
|
||||
$sValue = \file_get_contents($sPath);
|
||||
}
|
||||
|
||||
return \is_string($sValue) ? $sValue : '';
|
||||
}
|
||||
|
||||
public function Delete(string $sKey) : void
|
||||
{
|
||||
$sPath = $this->generateCachedFileName($sKey);
|
||||
if ('' !== $sPath && \file_exists($sPath))
|
||||
{
|
||||
if ('' !== $sPath && \file_exists($sPath)) {
|
||||
\unlink($sPath);
|
||||
}
|
||||
}
|
||||
|
||||
public function GC(int $iTimeToClearInHours = 24) : bool
|
||||
{
|
||||
if (0 === $iTimeToClearInHours) {
|
||||
\MailSo\Base\Utils::RecRmDir($this->sCacheFolder);
|
||||
} else {
|
||||
\MailSo\Base\Utils::RecTimeDirRemove($this->sCacheFolder, 3600 * $iTimeToClearInHours);
|
||||
}
|
||||
\MailSo\Base\Utils::RecTimeDirRemove($this->sCacheFolder, 3600 * $iTimeToClearInHours);
|
||||
return true;
|
||||
}
|
||||
|
||||
private function generateCachedFileName(string $sKey, bool $bMkDir = false) : string
|
||||
{
|
||||
$sFilePath = '';
|
||||
if (3 < \strlen($sKey))
|
||||
{
|
||||
if (3 < \strlen($sKey)) {
|
||||
$sKeyPath = \sha1($sKey);
|
||||
$sKeyPath = \substr($sKeyPath, 0, 2).'/'.\substr($sKeyPath, 2, 2).'/'.$sKeyPath;
|
||||
|
||||
$sFilePath = $this->sCacheFolder.$this->sKeyPrefix.$sKeyPath;
|
||||
$dir = \dirname($sFilePath);
|
||||
if ($bMkDir && !\is_dir($dir) && !\mkdir($dir, 0700, true))
|
||||
{
|
||||
$sFilePath = '';
|
||||
$sFilePath = $this->sCacheFolder . $this->sKeyPrefix
|
||||
. \substr($sKeyPath, 0, 2) . '/' . \substr($sKeyPath, 2, 2) . '/' . $sKeyPath;
|
||||
if ($bMkDir) {
|
||||
$dir = \dirname($sFilePath);
|
||||
if (!\is_dir($dir) && !\mkdir($dir, 0700, true)) {
|
||||
\error_log("mkdir({$dir}) failed");
|
||||
$sFilePath = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sFilePath;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,37 +18,27 @@ namespace MailSo\Cache\Drivers;
|
|||
*/
|
||||
class Memcache implements \MailSo\Cache\DriverInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iExpire;
|
||||
private int $iExpire;
|
||||
|
||||
/**
|
||||
* @var \Memcache|null
|
||||
* @var \Memcache|\Memcached|null
|
||||
*/
|
||||
private $oMem;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sKeyPrefix;
|
||||
private string $sKeyPrefix;
|
||||
|
||||
function __construct(string $sHost = '127.0.0.1', int $iPort = 11211, int $iExpire = 43200, string $sKeyPrefix = '')
|
||||
{
|
||||
$this->iExpire = 0 < $iExpire ? $iExpire : 43200;
|
||||
|
||||
$this->oMem = \class_exists('Memcache',false) ? new \Memcache : new \Memcached;
|
||||
if (!$this->oMem->addServer($sHost, \strpos($sHost, ':/') ? 0 : $this->iPort))
|
||||
{
|
||||
if (!$this->oMem->addServer($sHost, \strpos($sHost, ':/') ? 0 : $iPort)) {
|
||||
$this->oMem = null;
|
||||
}
|
||||
|
||||
$this->sKeyPrefix = $sKeyPrefix;
|
||||
if (!empty($this->sKeyPrefix))
|
||||
{
|
||||
$this->sKeyPrefix =
|
||||
\preg_replace('/[^a-zA-Z0-9_]/', '_', rtrim(trim($this->sKeyPrefix), '\\/')).'/';
|
||||
}
|
||||
$this->sKeyPrefix = empty($sKeyPrefix)
|
||||
? $sKeyPrefix
|
||||
: \preg_replace('/[^a-zA-Z0-9_]/', '_', \rtrim(\trim($this->sKeyPrefix), '\\/')) . '/';
|
||||
}
|
||||
|
||||
public function Set(string $sKey, string $sValue) : bool
|
||||
|
|
@ -64,19 +54,14 @@ class Memcache implements \MailSo\Cache\DriverInterface
|
|||
|
||||
public function Delete(string $sKey) : void
|
||||
{
|
||||
if ($this->oMem)
|
||||
{
|
||||
$this->oMem->delete($this->generateCachedKey($sKey));
|
||||
}
|
||||
$this->oMem && $this->oMem->delete($this->generateCachedKey($sKey));
|
||||
}
|
||||
|
||||
public function GC(int $iTimeToClearInHours = 24) : bool
|
||||
{
|
||||
if (0 === $iTimeToClearInHours && $this->oMem)
|
||||
{
|
||||
if (0 === $iTimeToClearInHours && $this->oMem) {
|
||||
return $this->oMem->flush();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,21 +18,14 @@ namespace MailSo\Cache\Drivers;
|
|||
*/
|
||||
class Redis implements \MailSo\Cache\DriverInterface
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iExpire;
|
||||
private int $iExpire;
|
||||
|
||||
/**
|
||||
* @var \Memcache|null
|
||||
* @var \Predis\Client|null
|
||||
*/
|
||||
private $oRedis;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sKeyPrefix;
|
||||
|
||||
private string $sKeyPrefix;
|
||||
|
||||
function __construct(string $sHost = '127.0.0.1', int $iPort = 6379, int $iExpire = 43200, string $sKeyPrefix = '')
|
||||
{
|
||||
|
|
@ -49,8 +42,7 @@ class Redis implements \MailSo\Cache\DriverInterface
|
|||
|
||||
$this->oRedis->connect();
|
||||
|
||||
if (!$this->oRedis->isConnected())
|
||||
{
|
||||
if (!$this->oRedis->isConnected()) {
|
||||
$this->oRedis = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -60,18 +52,14 @@ class Redis implements \MailSo\Cache\DriverInterface
|
|||
unset($oExc);
|
||||
}
|
||||
|
||||
$this->sKeyPrefix = $sKeyPrefix;
|
||||
if (!empty($this->sKeyPrefix))
|
||||
{
|
||||
$this->sKeyPrefix =
|
||||
\preg_replace('/[^a-zA-Z0-9_]/', '_', rtrim(trim($this->sKeyPrefix), '\\/')).'/';
|
||||
}
|
||||
$this->sKeyPrefix = empty($sKeyPrefix)
|
||||
? $sKeyPrefix
|
||||
: \preg_replace('/[^a-zA-Z0-9_]/', '_', rtrim(trim($sKeyPrefix), '\\/')) . '/';
|
||||
}
|
||||
|
||||
public function Set(string $sKey, string $sValue) : bool
|
||||
{
|
||||
if (!$this->oRedis)
|
||||
{
|
||||
if (!$this->oRedis) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -88,16 +76,14 @@ class Redis implements \MailSo\Cache\DriverInterface
|
|||
|
||||
public function Delete(string $sKey) : void
|
||||
{
|
||||
if ($this->oRedis)
|
||||
{
|
||||
if ($this->oRedis) {
|
||||
$this->oRedis->del($this->generateCachedKey($sKey));
|
||||
}
|
||||
}
|
||||
|
||||
public function GC(int $iTimeToClearInHours = 24) : bool
|
||||
{
|
||||
if (0 === $iTimeToClearInHours && $this->oRedis)
|
||||
{
|
||||
if (0 === $iTimeToClearInHours && $this->oRedis) {
|
||||
return $this->oRedis->flushdb();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,53 +16,5 @@ namespace MailSo;
|
|||
*/
|
||||
class Config
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public static $MessageListFastSimpleSearch = true;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public static $MessageListCountLimitTrigger = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public static $MessageListDateFilter = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $MessageListPermanentFilter = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public static $LargeThreadLimit = 50;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public static $MessageAllHeaders = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public static $LogSimpleLiterals = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public static $CheckNewMessages = true;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $BoundaryPrefix = '_Part_';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public static $ImapTimeout = 300;
|
||||
public static string $BoundaryPrefix = '_Part_';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2014 Usenko Timur
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
*/
|
||||
class Hooks
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
static $aCallbacks = array();
|
||||
|
||||
static public function Run(string $sName, array $aArg = array()) : void
|
||||
{
|
||||
if (isset(static::$aCallbacks[$sName]))
|
||||
{
|
||||
foreach (static::$aCallbacks[$sName] as $mCallback)
|
||||
{
|
||||
$mCallback(...$aArg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $mCallback
|
||||
*/
|
||||
static public function Add(string $sName, $mCallback) : void
|
||||
{
|
||||
if (\is_callable($mCallback))
|
||||
{
|
||||
if (!isset(static::$aCallbacks[$sName]))
|
||||
{
|
||||
static::$aCallbacks[$sName] = array();
|
||||
}
|
||||
|
||||
static::$aCallbacks[$sName][] = $mCallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,76 +11,42 @@
|
|||
|
||||
namespace MailSo\Imap;
|
||||
|
||||
use MailSo\Mime\ParameterCollection;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Imap
|
||||
*/
|
||||
class BodyStructure
|
||||
class BodyStructure implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sContentType;
|
||||
private string $sContentType;
|
||||
|
||||
private array $aContentTypeParams;
|
||||
|
||||
private string $sCharset;
|
||||
|
||||
private string $sContentID;
|
||||
|
||||
private string $sDescription;
|
||||
|
||||
private string $sMailEncodingName;
|
||||
|
||||
private string $sDisposition;
|
||||
|
||||
private string $sFileName;
|
||||
|
||||
private string $sLanguage = '';
|
||||
|
||||
private string $sLocation = '';
|
||||
|
||||
private int $iSize;
|
||||
|
||||
private string $sPartID;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* \MailSo\Imap\BodyStructure[]
|
||||
*/
|
||||
private $sCharset;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aBodyParams;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sContentID;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sDescription;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sMailEncodingName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sDisposition;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sFileName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sLanguage = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sLocation = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iSize;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sPartID;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aSubParts;
|
||||
private array $aSubParts;
|
||||
|
||||
public function MailEncodingName() : string
|
||||
{
|
||||
|
|
@ -92,9 +58,34 @@ class BodyStructure
|
|||
return $this->sPartID;
|
||||
}
|
||||
|
||||
public function FileName() : string
|
||||
public function FileName(bool $bCalculateOnEmpty = false) : string
|
||||
{
|
||||
return $this->sFileName;
|
||||
$sFileName = \trim($this->sFileName);
|
||||
if (\strlen($sFileName) || !$bCalculateOnEmpty) {
|
||||
return $sFileName;
|
||||
}
|
||||
|
||||
$sIdx = '-' . $this->PartID();
|
||||
|
||||
$sMimeType = $this->sContentType;
|
||||
if ('message/rfc822' === $sMimeType) {
|
||||
return "message{$sIdx}.eml";
|
||||
}
|
||||
if ('text/calendar' === $sMimeType) {
|
||||
return "calendar{$sIdx}.ics";
|
||||
}
|
||||
if ('text/plain' === $sMimeType) {
|
||||
return "part{$sIdx}.txt";
|
||||
}
|
||||
if (\preg_match('@text/(vcard|html|csv|xml|css|asp)@', $sMimeType, $aMatch)
|
||||
|| \preg_match('@image/(png|jpeg|gif|bmp|cgm|ief|tiff|webp)@', $sMimeType, $aMatch)) {
|
||||
return "part{$sIdx}.{$aMatch[1]}";
|
||||
}
|
||||
if (\strlen($sMimeType)) {
|
||||
return \str_replace('/', $sIdx.'.', $sMimeType);
|
||||
}
|
||||
|
||||
return ($this->isInline() ? 'inline' : 'part' ) . $sIdx;
|
||||
}
|
||||
|
||||
public function ContentType() : string
|
||||
|
|
@ -102,11 +93,6 @@ class BodyStructure
|
|||
return $this->sContentType;
|
||||
}
|
||||
|
||||
public function Size() : int
|
||||
{
|
||||
return $this->iSize;
|
||||
}
|
||||
|
||||
public function EstimatedSize() : int
|
||||
{
|
||||
$fCoefficient = 1;
|
||||
|
|
@ -128,52 +114,27 @@ class BodyStructure
|
|||
return $this->sCharset;
|
||||
}
|
||||
|
||||
public function ContentID() : string
|
||||
{
|
||||
return $this->sContentID;
|
||||
}
|
||||
|
||||
public function ContentLocation() : string
|
||||
{
|
||||
return $this->sLocation;
|
||||
}
|
||||
|
||||
public function SubParts() : array
|
||||
{
|
||||
return $this->aSubParts;
|
||||
}
|
||||
|
||||
public function IsInline() : bool
|
||||
public function isInline() : bool
|
||||
{
|
||||
return 'inline' === $this->sDisposition || \strlen($this->sContentID);
|
||||
}
|
||||
|
||||
public function IsImage() : bool
|
||||
public function isText() : bool
|
||||
{
|
||||
return 'image' === \MailSo\Base\Utils::ContentTypeType($this->sContentType, $this->sFileName);
|
||||
}
|
||||
|
||||
public function IsArchive() : bool
|
||||
{
|
||||
return 'archive' === \MailSo\Base\Utils::ContentTypeType($this->sContentType, $this->sFileName);
|
||||
}
|
||||
|
||||
public function IsPdf() : bool
|
||||
{
|
||||
return 'pdf' === \MailSo\Base\Utils::ContentTypeType($this->sContentType, $this->sFileName);
|
||||
}
|
||||
|
||||
public function IsDoc() : bool
|
||||
{
|
||||
return 'doc' === \MailSo\Base\Utils::ContentTypeType($this->sContentType, $this->sFileName);
|
||||
return 'text/html' === $this->sContentType || 'text/plain' === $this->sContentType;
|
||||
}
|
||||
|
||||
public function IsPgpEncrypted() : bool
|
||||
{
|
||||
// https://datatracker.ietf.org/doc/html/rfc3156#section-4
|
||||
return 'multipart/encrypted' === $this->sContentType
|
||||
&& !empty($this->aBodyParams['protocol'])
|
||||
&& 'application/pgp-encrypted' === \strtolower(\trim($this->aBodyParams['protocol']))
|
||||
&& !empty($this->aContentTypeParams['protocol'])
|
||||
&& 'application/pgp-encrypted' === \strtolower(\trim($this->aContentTypeParams['protocol']))
|
||||
// The multipart/encrypted body MUST consist of exactly two parts.
|
||||
&& 2 === \count($this->aSubParts)
|
||||
&& 'application/pgp-encrypted' === $this->aSubParts[0]->ContentType()
|
||||
|
|
@ -185,8 +146,8 @@ class BodyStructure
|
|||
{
|
||||
// https://datatracker.ietf.org/doc/html/rfc3156#section-5
|
||||
return 'multipart/signed' === $this->sContentType
|
||||
&& !empty($this->aBodyParams['protocol'])
|
||||
&& 'application/pgp-signature' === \strtolower(\trim($this->aBodyParams['protocol']))
|
||||
&& !empty($this->aContentTypeParams['protocol'])
|
||||
&& 'application/pgp-signature' === \strtolower(\trim($this->aContentTypeParams['protocol']))
|
||||
// The multipart/signed body MUST consist of exactly two parts.
|
||||
&& 2 === \count($this->aSubParts)
|
||||
&& $this->aSubParts[1]->IsPgpSignature();
|
||||
|
|
@ -197,22 +158,21 @@ class BodyStructure
|
|||
return \in_array($this->sContentType, ['application/pgp-signature', 'application/pkcs7-signature']);
|
||||
}
|
||||
|
||||
public function IsAttachBodyPart() : bool
|
||||
public function IsAttachment() : bool
|
||||
{
|
||||
return 'application/pgp-encrypted' !== $this->sContentType
|
||||
&& (
|
||||
'attachment' === $this->sDisposition || (
|
||||
!\str_starts_with($this->sContentType, 'multipart/')
|
||||
&& 'text/html' !== $this->sContentType
|
||||
&& 'text/plain' !== $this->sContentType
|
||||
&& !$this->isText()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function IsFlowedFormat() : bool
|
||||
{
|
||||
return !empty($this->aBodyParams['format'])
|
||||
&& 'flowed' === \strtolower(\trim($this->aBodyParams['format']))
|
||||
return !empty($this->aContentTypeParams['format'])
|
||||
&& 'flowed' === \strtolower(\trim($this->aContentTypeParams['format']))
|
||||
&& !\in_array($this->sMailEncodingName, array('base64', 'quoted-printable'));
|
||||
}
|
||||
|
||||
|
|
@ -221,8 +181,7 @@ class BodyStructure
|
|||
$aParts = [];
|
||||
|
||||
$gParts = $this->SearchByCallback(function ($oItem) {
|
||||
return ('text/html' === $oItem->sContentType || 'text/plain' === $oItem->sContentType)
|
||||
&& !$oItem->IsAttachBodyPart();
|
||||
return $oItem->isText() && !$oItem->IsAttachment();
|
||||
});
|
||||
foreach ($gParts as $oPart) {
|
||||
$aParts[] = $oPart;
|
||||
|
|
@ -235,7 +194,7 @@ class BodyStructure
|
|||
if (!$aParts) {
|
||||
$gEncryptedParts = $this->SearchByContentType('multipart/encrypted');
|
||||
foreach ($gEncryptedParts as $oPart) {
|
||||
if ($oPart->IsPgpEncrypted() && $oPart->SubParts()[1]->IsInline()) {
|
||||
if ($oPart->IsPgpEncrypted() && $oPart->SubParts()[1]->isInline()) {
|
||||
return array($oPart->SubParts()[1]);
|
||||
}
|
||||
}
|
||||
|
|
@ -247,25 +206,19 @@ class BodyStructure
|
|||
public function SearchCharset() : string
|
||||
{
|
||||
$gParts = $this->SearchByCallback(function ($oPart) {
|
||||
return $oPart->Charset()
|
||||
&& ('text/html' === $oPart->sContentType || 'text/plain' === $oPart->sContentType)
|
||||
&& !$oPart->IsAttachBodyPart();
|
||||
return $oPart->Charset() && $oPart->isText() && !$oPart->IsAttachment();
|
||||
});
|
||||
|
||||
if (!$gParts->valid()) {
|
||||
$gParts = $this->SearchByCallback(function ($oPart) {
|
||||
return $oPart->Charset() && $oPart->IsAttachBodyPart();
|
||||
return $oPart->Charset() && $oPart->IsAttachment();
|
||||
});
|
||||
}
|
||||
|
||||
return $gParts->valid() ? $gParts->current()->Charset() : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $fCallback
|
||||
*/
|
||||
// public function SearchByCallback($fCallback) : \Generator
|
||||
public function SearchByCallback($fCallback, $parent = null) : iterable
|
||||
public function SearchByCallback(callable $fCallback, /*BodyStructure*/ $parent = null) : iterable
|
||||
{
|
||||
if ($fCallback($this, $parent)) {
|
||||
yield $this;
|
||||
|
|
@ -278,8 +231,8 @@ class BodyStructure
|
|||
public function SearchAttachmentsParts() : iterable
|
||||
{
|
||||
return $this->SearchByCallback(function ($oItem, $oParent) {
|
||||
// return $oItem->IsAttachBodyPart();
|
||||
return $oItem->IsAttachBodyPart() && (!$oParent || !$oParent->IsPgpEncrypted());
|
||||
// return $oItem->IsAttachment();
|
||||
return $oItem->IsAttachment() && (!$oParent || !$oParent->IsPgpEncrypted());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -294,20 +247,15 @@ class BodyStructure
|
|||
public function GetPartByMimeIndex(string $sMimeIndex) : self
|
||||
{
|
||||
$oPart = null;
|
||||
if (\strlen($sMimeIndex))
|
||||
{
|
||||
if ($sMimeIndex === $this->sPartID)
|
||||
{
|
||||
if (\strlen($sMimeIndex)) {
|
||||
if ($sMimeIndex === $this->sPartID) {
|
||||
$oPart = $this;
|
||||
}
|
||||
|
||||
if (null === $oPart)
|
||||
{
|
||||
foreach ($this->aSubParts as /* @var $oSubPart \MailSo\Imap\BodyStructure */ $oSubPart)
|
||||
{
|
||||
if (null === $oPart) {
|
||||
foreach ($this->aSubParts as /* @var $oSubPart \MailSo\Imap\BodyStructure */ $oSubPart) {
|
||||
$oPart = $oSubPart->GetPartByMimeIndex($sMimeIndex);
|
||||
if (null !== $oPart)
|
||||
{
|
||||
if (null !== $oPart) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -320,60 +268,43 @@ class BodyStructure
|
|||
private static function decodeAttrParameter(array $aParams, string $sParamName, string $sCharset) : string
|
||||
{
|
||||
$sResult = '';
|
||||
if (isset($aParams[$sParamName]))
|
||||
{
|
||||
if (isset($aParams[$sParamName])) {
|
||||
$sResult = \MailSo\Base\Utils::DecodeHeaderValue($aParams[$sParamName], $sCharset);
|
||||
}
|
||||
else if (isset($aParams[$sParamName.'*']))
|
||||
{
|
||||
} else if (isset($aParams[$sParamName.'*'])) {
|
||||
$aValueParts = \explode("''", $aParams[$sParamName.'*'], 2);
|
||||
if (2 === \count($aValueParts))
|
||||
{
|
||||
if (2 === \count($aValueParts)) {
|
||||
$sCharset = isset($aValueParts[0]) ? $aValueParts[0] : \MailSo\Base\Enumerations\Charset::UTF_8;
|
||||
|
||||
$sResult = \MailSo\Base\Utils::ConvertEncoding(
|
||||
\urldecode($aValueParts[1]), $sCharset, \MailSo\Base\Enumerations\Charset::UTF_8);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sResult = \urldecode($aParams[$sParamName.'*']);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sCharset = '';
|
||||
$sCharsetIndex = -1;
|
||||
|
||||
$aFileNames = array();
|
||||
foreach ($aParams as $sName => $sValue)
|
||||
{
|
||||
foreach ($aParams as $sName => $sValue) {
|
||||
$aMatches = array();
|
||||
if (\preg_match('/^'.\preg_quote($sParamName, '/').'\*([0-9]+)\*$/i', $sName, $aMatches))
|
||||
{
|
||||
if (\preg_match('/^'.\preg_quote($sParamName, '/').'\*([0-9]+)\*$/i', $sName, $aMatches)) {
|
||||
$iIndex = (int) $aMatches[1];
|
||||
if ($sCharsetIndex < $iIndex && false !== \strpos($sValue, "''"))
|
||||
{
|
||||
if ($sCharsetIndex < $iIndex && false !== \strpos($sValue, "''")) {
|
||||
$aValueParts = \explode("''", $sValue, 2);
|
||||
if (2 === \count($aValueParts) && \strlen($aValueParts[0]))
|
||||
{
|
||||
if (2 === \count($aValueParts) && \strlen($aValueParts[0])) {
|
||||
$sCharsetIndex = $iIndex;
|
||||
$sCharset = $aValueParts[0];
|
||||
$sValue = $aValueParts[1];
|
||||
}
|
||||
}
|
||||
|
||||
$aFileNames[$iIndex] = $sValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($aFileNames))
|
||||
{
|
||||
if (\count($aFileNames)) {
|
||||
\ksort($aFileNames, SORT_NUMERIC);
|
||||
$sResult = \implode(\array_values($aFileNames));
|
||||
$sResult = \urldecode($sResult);
|
||||
|
||||
if (\strlen($sCharset))
|
||||
{
|
||||
if (\strlen($sCharset)) {
|
||||
$sResult = \MailSo\Base\Utils::ConvertEncoding($sResult,
|
||||
$sCharset, \MailSo\Base\Enumerations\Charset::UTF_8);
|
||||
}
|
||||
|
|
@ -385,16 +316,15 @@ class BodyStructure
|
|||
|
||||
public static function NewInstance(array $aBodyStructure, string $sPartID = '') : ?self
|
||||
{
|
||||
if (2 > \count($aBodyStructure))
|
||||
{
|
||||
if (2 > \count($aBodyStructure)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sContentTypeMain = '';
|
||||
$sContentTypeSub = '';
|
||||
$aSubParts = array();
|
||||
$aBodyParams = array();
|
||||
$sName = '';
|
||||
$aContentTypeParams = array();
|
||||
$sFileName = '';
|
||||
$sCharset = ''; // \MailSo\Base\Enumerations\Charset::UTF_8 ?
|
||||
$sContentID = '';
|
||||
$sDescription = '';
|
||||
|
|
@ -402,20 +332,16 @@ class BodyStructure
|
|||
$iSize = 0;
|
||||
$iExtraItemPos = 0; // list index of items which have no well-established position (such as 0, 1, 5, etc).
|
||||
|
||||
if (\is_array($aBodyStructure[0]))
|
||||
{
|
||||
if (\is_array($aBodyStructure[0])) {
|
||||
// Process multipart body structure
|
||||
$sContentTypeMain = 'multipart';
|
||||
$sContentTypeSub = 'mixed'; // primary default
|
||||
$sSubPartIDPrefix = '';
|
||||
if (!\strlen($sPartID) || '.' === $sPartID[\strlen($sPartID) - 1])
|
||||
{
|
||||
if (!\strlen($sPartID) || '.' === $sPartID[\strlen($sPartID) - 1]) {
|
||||
// This multi-part is root part of message.
|
||||
$sSubPartIDPrefix = $sPartID;
|
||||
$sPartID .= 'TEXT';
|
||||
}
|
||||
else if (\strlen($sPartID))
|
||||
{
|
||||
} else if (\strlen($sPartID)) {
|
||||
// This multi-part is a part of another multi-part.
|
||||
$sSubPartIDPrefix = $sPartID.'.';
|
||||
}
|
||||
|
|
@ -427,18 +353,15 @@ class BodyStructure
|
|||
("text" "plain" ("charset" "utf-8") …)
|
||||
("text" "html" …)
|
||||
*/
|
||||
while ($iExtraItemPos < \count($aBodyStructure) && \is_array($aBodyStructure[$iExtraItemPos]))
|
||||
{
|
||||
while ($iExtraItemPos < \count($aBodyStructure) && \is_array($aBodyStructure[$iExtraItemPos])) {
|
||||
$oPart = self::NewInstance($aBodyStructure[$iExtraItemPos], $sSubPartIDPrefix.$iIndex);
|
||||
if (!$oPart)
|
||||
{
|
||||
if (!$oPart) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// For multipart, we have no charset info in the part itself. Thus,
|
||||
// obtain charset from nested parts.
|
||||
if (!$sCharset)
|
||||
{
|
||||
if (!$sCharset) {
|
||||
$sCharset = $oPart->Charset();
|
||||
}
|
||||
|
||||
|
|
@ -451,26 +374,20 @@ class BodyStructure
|
|||
* Now process the subparts containter like:
|
||||
"alternative" ("boundary" "--boundary_id") …
|
||||
*/
|
||||
if ($iExtraItemPos < \count($aBodyStructure))
|
||||
{
|
||||
if (!\is_string($aBodyStructure[$iExtraItemPos]))
|
||||
{
|
||||
if ($iExtraItemPos < \count($aBodyStructure)) {
|
||||
if (!\is_string($aBodyStructure[$iExtraItemPos])) {
|
||||
return null;
|
||||
}
|
||||
$sContentTypeSub = \strtolower($aBodyStructure[$iExtraItemPos]);
|
||||
|
||||
++$iExtraItemPos;
|
||||
if ($iExtraItemPos < \count($aBodyStructure) && \is_array($aBodyStructure[$iExtraItemPos]))
|
||||
{
|
||||
$aBodyParams = self::getKeyValueListFromArrayList($aBodyStructure[$iExtraItemPos]);
|
||||
if ($iExtraItemPos < \count($aBodyStructure) && \is_array($aBodyStructure[$iExtraItemPos])) {
|
||||
$aContentTypeParams = self::getKeyValueListFromArrayList($aBodyStructure[$iExtraItemPos]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (\is_string($aBodyStructure[0]))
|
||||
{
|
||||
} else if (\is_string($aBodyStructure[0])) {
|
||||
// Process simple (singlepart) body structure
|
||||
if (7 > \count($aBodyStructure) || !\is_string($aBodyStructure[1]))
|
||||
{
|
||||
if (7 > \count($aBodyStructure) || !\is_string($aBodyStructure[1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -478,39 +395,33 @@ class BodyStructure
|
|||
$sContentTypeSub = \strtolower($aBodyStructure[1]);
|
||||
|
||||
$aBodyParamList = $aBodyStructure[2];
|
||||
if (\is_array($aBodyParamList))
|
||||
{
|
||||
$aBodyParams = self::getKeyValueListFromArrayList($aBodyParamList);
|
||||
if (isset($aBodyParams['charset']))
|
||||
{
|
||||
$sCharset = $aBodyParams['charset'];
|
||||
if (\is_array($aBodyParamList)) {
|
||||
$aContentTypeParams = self::getKeyValueListFromArrayList($aBodyParamList);
|
||||
if (isset($aContentTypeParams['charset'])) {
|
||||
$sCharset = $aContentTypeParams['charset'];
|
||||
}
|
||||
$sFileName = self::decodeAttrParameter($aContentTypeParams, 'name', $sCharset);
|
||||
if ($sFileName) {
|
||||
$aContentTypeParams['name'] = $sFileName;
|
||||
}
|
||||
|
||||
$sName = self::decodeAttrParameter($aBodyParams, 'name', $sCharset);
|
||||
}
|
||||
|
||||
if (null !== $aBodyStructure[3])
|
||||
{
|
||||
if (!\is_string($aBodyStructure[3]))
|
||||
{
|
||||
if (null !== $aBodyStructure[3]) {
|
||||
if (!\is_string($aBodyStructure[3])) {
|
||||
return null;
|
||||
}
|
||||
$sContentID = $aBodyStructure[3];
|
||||
}
|
||||
|
||||
if (null !== $aBodyStructure[4])
|
||||
{
|
||||
if (!\is_string($aBodyStructure[4]))
|
||||
{
|
||||
if (null !== $aBodyStructure[4]) {
|
||||
if (!\is_string($aBodyStructure[4])) {
|
||||
return null;
|
||||
}
|
||||
$sDescription = $aBodyStructure[4];
|
||||
}
|
||||
|
||||
if (null !== $aBodyStructure[5])
|
||||
{
|
||||
if (!\is_string($aBodyStructure[5]))
|
||||
{
|
||||
if (null !== $aBodyStructure[5]) {
|
||||
if (!\is_string($aBodyStructure[5])) {
|
||||
return null;
|
||||
}
|
||||
$sMailEncodingName = $aBodyStructure[5];
|
||||
|
|
@ -518,24 +429,20 @@ class BodyStructure
|
|||
|
||||
$iSize = \is_numeric($aBodyStructure[6]) ? (int) $aBodyStructure[6] : -1;
|
||||
|
||||
if (!\strlen($sPartID) || '.' === $sPartID[\strlen($sPartID) - 1])
|
||||
{
|
||||
if (!\strlen($sPartID) || '.' === $sPartID[\strlen($sPartID) - 1]) {
|
||||
// This is the only sub-part of the message (otherwise, it would be
|
||||
// one of sub-parts of a multi-part, and partID would already be fully set up).
|
||||
$sPartID .= '1';
|
||||
}
|
||||
|
||||
$iExtraItemPos = 7;
|
||||
if ('text' === $sContentTypeMain)
|
||||
{
|
||||
if ('text' === $sContentTypeMain) {
|
||||
/**
|
||||
* A body type of type TEXT contains, immediately after the basic
|
||||
* fields, the size of the body in text lines.
|
||||
*/
|
||||
++$iExtraItemPos;
|
||||
}
|
||||
else if ('message' === $sContentTypeMain && 'rfc822' === $sContentTypeSub)
|
||||
{
|
||||
} else if ('message' === $sContentTypeMain && 'rfc822' === $sContentTypeSub) {
|
||||
/**
|
||||
* A body type of type MESSAGE and subtype RFC822 contains,
|
||||
* immediately after the basic fields, the envelope structure,
|
||||
|
|
@ -543,9 +450,7 @@ class BodyStructure
|
|||
*/
|
||||
$iExtraItemPos += 3;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -553,20 +458,15 @@ class BodyStructure
|
|||
++$iExtraItemPos;
|
||||
|
||||
$sDisposition = '';
|
||||
$sFileName = '';
|
||||
|
||||
if ($iExtraItemPos < \count($aBodyStructure))
|
||||
{
|
||||
if ($iExtraItemPos < \count($aBodyStructure)) {
|
||||
$aDispList = $aBodyStructure[$iExtraItemPos];
|
||||
if (\is_array($aDispList) && 1 < \count($aDispList))
|
||||
{
|
||||
if (!\is_string($aDispList[0]))
|
||||
{
|
||||
if (\is_array($aDispList) && 1 < \count($aDispList)) {
|
||||
if (!\is_string($aDispList[0])) {
|
||||
return null;
|
||||
}
|
||||
$sDisposition = $aDispList[0];
|
||||
if (\is_array($aDispList[1]))
|
||||
{
|
||||
if (\is_array($aDispList[1])) {
|
||||
$aDispositionParams = self::getKeyValueListFromArrayList($aDispList[1]);
|
||||
$sFileName = self::decodeAttrParameter($aDispositionParams, 'filename', $sCharset);
|
||||
}
|
||||
|
|
@ -575,32 +475,27 @@ class BodyStructure
|
|||
}
|
||||
|
||||
$oStructure = new self;
|
||||
$oStructure->sContentType = \strtolower($sContentTypeMain.'/'.$sContentTypeSub);
|
||||
$oStructure->sContentType = \strtolower(\trim($sContentTypeMain.'/'.$sContentTypeSub));
|
||||
$oStructure->aContentTypeParams = $aContentTypeParams;
|
||||
$oStructure->sCharset = $sCharset;
|
||||
$oStructure->aBodyParams = $aBodyParams;
|
||||
$oStructure->sContentID = $sContentID;
|
||||
$oStructure->sContentID = \trim($sContentID);
|
||||
$oStructure->sDescription = $sDescription;
|
||||
$oStructure->sMailEncodingName = \strtolower($sMailEncodingName);
|
||||
$oStructure->sDisposition = \strtolower($sDisposition);
|
||||
$oStructure->sFileName = \MailSo\Base\Utils::Utf8Clear($sFileName ?: $sName);
|
||||
$oStructure->sFileName = \MailSo\Base\Utils::Utf8Clear($sFileName);
|
||||
$oStructure->iSize = $iSize;
|
||||
$oStructure->sPartID = $sPartID;
|
||||
$oStructure->aSubParts = $aSubParts;
|
||||
|
||||
if ($iExtraItemPos < \count($aBodyStructure))
|
||||
{
|
||||
if (\is_array($aBodyStructure[$iExtraItemPos]))
|
||||
{
|
||||
if ($iExtraItemPos < \count($aBodyStructure)) {
|
||||
if (\is_array($aBodyStructure[$iExtraItemPos])) {
|
||||
$oStructure->sLanguage = \implode(',', $aBodyStructure[$iExtraItemPos]);
|
||||
}
|
||||
else if (\is_string($aBodyStructure[$iExtraItemPos]))
|
||||
{
|
||||
} else if (\is_string($aBodyStructure[$iExtraItemPos])) {
|
||||
$oStructure->sLanguage = $aBodyStructure[$iExtraItemPos];
|
||||
}
|
||||
++$iExtraItemPos;
|
||||
|
||||
if ($iExtraItemPos < \count($aBodyStructure) && \is_string($aBodyStructure[$iExtraItemPos]))
|
||||
{
|
||||
if ($iExtraItemPos < \count($aBodyStructure) && \is_string($aBodyStructure[$iExtraItemPos])) {
|
||||
$oStructure->sLocation = $aBodyStructure[$iExtraItemPos];
|
||||
}
|
||||
}
|
||||
|
|
@ -616,12 +511,9 @@ class BodyStructure
|
|||
{
|
||||
$aDict = array();
|
||||
$iLen = \count($aList);
|
||||
if (0 === ($iLen % 2))
|
||||
{
|
||||
for ($iIndex = 0; $iIndex < $iLen; $iIndex += 2)
|
||||
{
|
||||
if (\is_string($aList[$iIndex]) && \is_string($aList[$iIndex + 1]))
|
||||
{
|
||||
if (0 === ($iLen % 2)) {
|
||||
for ($iIndex = 0; $iIndex < $iLen; $iIndex += 2) {
|
||||
if (\is_string($aList[$iIndex]) && \is_string($aList[$iIndex + 1])) {
|
||||
$aDict[\strtolower($aList[$iIndex])] = $aList[$iIndex + 1];
|
||||
}
|
||||
}
|
||||
|
|
@ -629,4 +521,19 @@ class BodyStructure
|
|||
|
||||
return $aDict;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return array(
|
||||
'mimeIndex' => $this->sPartID,
|
||||
'mimeType' => $this->sContentType,
|
||||
// 'mimeTypeParams' => $this->aContentTypeParams,
|
||||
'fileName' => \MailSo\Base\Utils::SecureFileName($this->FileName(true)),
|
||||
'estimatedSize' => $this->EstimatedSize(),
|
||||
'cId' => $this->sContentID,
|
||||
'contentLocation' => $this->sLocation,
|
||||
'isInline' => $this->isInline()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ trait ACL
|
|||
*/
|
||||
public function ACLAllow(string $sFolderName, string $command) : bool
|
||||
{
|
||||
if ($this->IsSupported('ACL') || $this->IsSupported('RIGHTS=texk')) {
|
||||
if ($this->hasCapability('ACL') || $this->CapabilityValue('RIGHTS')) {
|
||||
if ('MYRIGHTS' === $command) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -128,7 +128,8 @@ trait ACL
|
|||
|
||||
public function FolderMyRights(string $sFolderName) : ?ACLResponse
|
||||
{
|
||||
// if ($this->IsSupported('ACL')) {
|
||||
// if ($this->ACLAllow($sFolderName, 'MYRIGHTS')) {
|
||||
// if ($this->hasCapability('ACL')) {
|
||||
$oResponses = $this->SendRequestGetResponse('MYRIGHTS', array($this->EscapeString($sFolderName)));
|
||||
foreach ($oResponses as $oResponse) {
|
||||
if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType
|
||||
|
|
|
|||
|
|
@ -13,10 +13,12 @@
|
|||
namespace MailSo\Imap\Commands;
|
||||
|
||||
use MailSo\Imap\Folder;
|
||||
use MailSo\Imap\FolderCollection;
|
||||
use MailSo\Imap\FolderInformation;
|
||||
use MailSo\Imap\SequenceSet;
|
||||
use MailSo\Imap\Enumerations\FolderStatus;
|
||||
use MailSo\Imap\Enumerations\FolderResponseStatus;
|
||||
use MailSo\Imap\Enumerations\MessageFlag;
|
||||
use MailSo\Imap\Enumerations\StoreAction;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
|
|
@ -24,19 +26,33 @@ use MailSo\Imap\Enumerations\FolderResponseStatus;
|
|||
*/
|
||||
trait Folders
|
||||
{
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function FolderClear(string $sFolderFullName) : void
|
||||
{
|
||||
if (0 < $this->FolderSelect($sFolderFullName)->MESSAGES) {
|
||||
$this->MessageStoreFlag(new SequenceSet('1:*', false),
|
||||
array(MessageFlag::DELETED),
|
||||
StoreAction::ADD_FLAGS_SILENT
|
||||
);
|
||||
$this->FolderExpunge();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderCreate(string $sFolderName) : self
|
||||
public function FolderCreate(string $sFolderName, bool $bSubscribe = false) : void
|
||||
{
|
||||
$this->SendRequestGetResponse('CREATE', array(
|
||||
$this->EscapeFolderName($sFolderName)
|
||||
// , ['(USE (\Drafts \Sent))'] RFC 6154
|
||||
));
|
||||
return $this;
|
||||
$bSubscribe && $this->FolderSubscribe($sFolderName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -45,23 +61,34 @@ trait Folders
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderDelete(string $sFolderName) : self
|
||||
public function FolderDelete(string $sFolderName) : void
|
||||
{
|
||||
if (!$sFolderName || 'INBOX' === $sFolderName) {
|
||||
throw new \InvalidArgumentException;
|
||||
}
|
||||
|
||||
$oInfo = $this->hasCapability('IMAP4rev2')
|
||||
? $this->FolderExamine($sFolderName)
|
||||
: $this->FolderStatus($sFolderName);
|
||||
if ($oInfo->MESSAGES) {
|
||||
throw new \MailSo\Mail\Exceptions\NonEmptyFolder;
|
||||
}
|
||||
|
||||
$this->FolderUnsubscribe($sFolderName);
|
||||
$this->FolderUnselect();
|
||||
|
||||
// Uncomment will work issue #124 ?
|
||||
// $this->selectOrExamineFolder($sFolderName, true);
|
||||
$this->SendRequestGetResponse('DELETE',
|
||||
array($this->EscapeFolderName($sFolderName)));
|
||||
$this->SendRequestGetResponse('DELETE', [$this->EscapeFolderName($sFolderName)]);
|
||||
// $this->FolderCheck();
|
||||
// $this->FolderUnselect();
|
||||
|
||||
// Will this workaround solve Dovecot issue #124 ?
|
||||
try {
|
||||
$this->FolderRename($sFolderName, "{$sFolderName}-dummy");
|
||||
$this->FolderRename("{$sFolderName}-dummy", $sFolderName);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Throwable $oException) {
|
||||
$this->writeLogException($oException, \LOG_WARNING, false);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -70,11 +97,9 @@ trait Folders
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderSubscribe(string $sFolderName) : self
|
||||
public function FolderSubscribe(string $sFolderName) : void
|
||||
{
|
||||
$this->SendRequestGetResponse('SUBSCRIBE',
|
||||
array($this->EscapeFolderName($sFolderName)));
|
||||
return $this;
|
||||
$this->SendRequestGetResponse('SUBSCRIBE', [$this->EscapeFolderName($sFolderName)]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -83,11 +108,9 @@ trait Folders
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderUnsubscribe(string $sFolderName) : self
|
||||
public function FolderUnsubscribe(string $sFolderName) : void
|
||||
{
|
||||
$this->SendRequestGetResponse('UNSUBSCRIBE',
|
||||
array($this->EscapeFolderName($sFolderName)));
|
||||
return $this;
|
||||
$this->SendRequestGetResponse('UNSUBSCRIBE', [$this->EscapeFolderName($sFolderName)]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -96,12 +119,44 @@ trait Folders
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderRename(string $sOldFolderName, string $sNewFolderName) : self
|
||||
public function FolderRename(string $sOldFolderName, string $sNewFolderName) : void
|
||||
{
|
||||
$this->SendRequestGetResponse('RENAME', array(
|
||||
$this->SendRequestGetResponse('RENAME', [
|
||||
$this->EscapeFolderName($sOldFolderName),
|
||||
$this->EscapeFolderName($sNewFolderName)));
|
||||
return $this;
|
||||
$this->EscapeFolderName($sNewFolderName)
|
||||
]);
|
||||
}
|
||||
|
||||
private function FolderStatusItems() : array
|
||||
{
|
||||
$aStatusItems = array(
|
||||
FolderStatus::MESSAGES,
|
||||
FolderStatus::UNSEEN,
|
||||
FolderStatus::UIDNEXT,
|
||||
FolderStatus::UIDVALIDITY
|
||||
);
|
||||
// RFC 4551
|
||||
if ($this->hasCapability('CONDSTORE')) {
|
||||
$aStatusItems[] = FolderStatus::HIGHESTMODSEQ;
|
||||
}
|
||||
// RFC 7889
|
||||
if ($this->hasCapability('APPENDLIMIT')) {
|
||||
$aStatusItems[] = FolderStatus::APPENDLIMIT;
|
||||
}
|
||||
// RFC 8474
|
||||
if ($this->hasCapability('OBJECTID')) {
|
||||
$aStatusItems[] = FolderStatus::MAILBOXID;
|
||||
/*
|
||||
} else if ($this->hasCapability('X-DOVECOT')) {
|
||||
$aStatusItems[] = 'X-GUID';
|
||||
*/
|
||||
}
|
||||
/* // STATUS SIZE can take a significant amount of time, therefore not active
|
||||
if ($this->hasCapability('IMAP4rev2')) {
|
||||
$aStatusItems[] = FolderStatus::SIZE;
|
||||
}
|
||||
*/
|
||||
return $aStatusItems;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -112,48 +167,26 @@ trait Folders
|
|||
*
|
||||
* https://datatracker.ietf.org/doc/html/rfc9051#section-6.3.11
|
||||
*/
|
||||
public function FolderStatus(string $sFolderName) : FolderInformation
|
||||
public function FolderStatus(string $sFolderName, bool $bSelect = false) : FolderInformation
|
||||
{
|
||||
$aStatusItems = array(
|
||||
FolderResponseStatus::MESSAGES,
|
||||
FolderResponseStatus::UNSEEN,
|
||||
FolderResponseStatus::UIDNEXT,
|
||||
FolderResponseStatus::UIDVALIDITY
|
||||
);
|
||||
if ($this->IsSupported('CONDSTORE')) {
|
||||
$aStatusItems[] = FolderResponseStatus::HIGHESTMODSEQ;
|
||||
}
|
||||
if ($this->IsSupported('APPENDLIMIT')) {
|
||||
$aStatusItems[] = FolderResponseStatus::APPENDLIMIT;
|
||||
}
|
||||
if ($this->IsSupported('OBJECTID')) {
|
||||
$aStatusItems[] = FolderResponseStatus::MAILBOXID;
|
||||
/*
|
||||
} else if ($this->IsSupported('X-DOVECOT')) {
|
||||
$aStatusItems[] = 'X-GUID';
|
||||
*/
|
||||
}
|
||||
/* // STATUS SIZE can take a significant amount of time, therefore not active
|
||||
if ($this->IsSupported('IMAP4rev2')) {
|
||||
$aStatusItems[] = FolderResponseStatus::SIZE;
|
||||
}
|
||||
*/
|
||||
$oFolderInfo = $this->oCurrentFolderInfo;
|
||||
$bReselect = false;
|
||||
$bWritable = false;
|
||||
if ($oFolderInfo && $sFolderName === $oFolderInfo->FolderName) {
|
||||
if ($oFolderInfo && $sFolderName === $oFolderInfo->FullName) {
|
||||
if ($oFolderInfo->hasStatus) {
|
||||
return $oFolderInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* There's a long standing IMAP CLIENTBUG where STATUS command is executed
|
||||
* after SELECT/EXAMINE on same folder (it should not).
|
||||
* So we must unselect the folder to be able to get the APPENDLIMIT and UNSEEN.
|
||||
*/
|
||||
/*
|
||||
if ($this->IsSupported('ESEARCH')) {
|
||||
$aResult = $oFolderInfo->getStatusItems();
|
||||
// SELECT or EXAMINE command then UNSEEN is the message sequence number of the first unseen message
|
||||
$aResult['UNSEEN'] = $this->MessageSimpleESearch('UNSEEN', ['COUNT'])['COUNT'];
|
||||
return $aResult;
|
||||
if ($this->hasCapability('ESEARCH') && !isset($oFolderInfo->UNSEEN)) {
|
||||
$oFolderInfo->UNSEEN = $this->MessageSimpleESearch('UNSEEN', ['COUNT'])['COUNT'];
|
||||
}
|
||||
return $oFolderInfo;
|
||||
*/
|
||||
$bWritable = $oFolderInfo->IsWritable;
|
||||
$bReselect = true;
|
||||
|
|
@ -161,30 +194,49 @@ trait Folders
|
|||
}
|
||||
|
||||
$oInfo = new FolderInformation($sFolderName, false);
|
||||
$this->SendRequest('STATUS', array($this->EscapeFolderName($sFolderName), $aStatusItems));
|
||||
$this->SendRequest('STATUS', array($this->EscapeFolderName($sFolderName), $this->FolderStatusItems()));
|
||||
foreach ($this->yieldUntaggedResponses() as $oResponse) {
|
||||
$oInfo->setStatusFromResponse($oResponse);
|
||||
}
|
||||
|
||||
if ($bReselect) {
|
||||
$this->selectOrExamineFolder($sFolderName, $bWritable, false);
|
||||
// $this->oCurrentFolderInfo->UNSEEN = $oInfo->UNSEEN;
|
||||
if ($bReselect || $bSelect) {
|
||||
// Don't use FolderExamine, else PERMANENTFLAGS is empty in Dovecot
|
||||
$oFolderInfo = $this->selectOrExamineFolder($sFolderName, $bSelect || $bWritable, false);
|
||||
$oFolderInfo->MESSAGES = \max(0, $oFolderInfo->MESSAGES, $oInfo->MESSAGES);
|
||||
// SELECT or EXAMINE command then UNSEEN is the message sequence number of the first unseen message.
|
||||
// And deprecated in IMAP4rev2, so we set it to the amount of unseen messages
|
||||
$oFolderInfo->UNSEEN = \max(0, $oInfo->UNSEEN);
|
||||
$oFolderInfo->UIDNEXT = \max(0, $oFolderInfo->UIDNEXT, $oInfo->UIDNEXT);
|
||||
$oFolderInfo->UIDVALIDITY = \max(0, $oFolderInfo->UIDVALIDITY, $oInfo->UIDVALIDITY);
|
||||
$oFolderInfo->HIGHESTMODSEQ = \max(0, $oInfo->HIGHESTMODSEQ);
|
||||
$oFolderInfo->APPENDLIMIT = \max(0, $oFolderInfo->APPENDLIMIT, $oInfo->APPENDLIMIT);
|
||||
$oFolderInfo->MAILBOXID = $oFolderInfo->MAILBOXID ?: $oInfo->MAILBOXID;
|
||||
// $oFolderInfo->SIZE = \max($oFolderInfo->SIZE, $oInfo->SIZE);
|
||||
// $oFolderInfo->RECENT = \max(0, $oFolderInfo->RECENT, $oInfo->RECENT);
|
||||
$oFolderInfo->hasStatus = $oInfo->hasStatus;
|
||||
$oFolderInfo->generateETag($this);
|
||||
return $oFolderInfo;
|
||||
}
|
||||
|
||||
$oInfo->generateETag($this);
|
||||
return $oInfo;
|
||||
}
|
||||
|
||||
public function FolderStatusAndSelect(string $sFolderName) : FolderInformation
|
||||
{
|
||||
return $this->FolderStatus($sFolderName, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderCheck() : self
|
||||
public function FolderCheck() : void
|
||||
{
|
||||
if ($this->IsSelected()) {
|
||||
$this->SendRequestGetResponse('CHECK');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -209,25 +261,21 @@ trait Folders
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderUnselect() : self
|
||||
public function FolderUnselect() : void
|
||||
{
|
||||
if ($this->IsSelected()) {
|
||||
if ($this->IsSupported('UNSELECT')) {
|
||||
if ($this->hasCapability('UNSELECT')) {
|
||||
$this->SendRequestGetResponse('UNSELECT');
|
||||
$this->oCurrentFolderInfo = null;
|
||||
} else {
|
||||
try {
|
||||
$this->SendRequestGetResponse('SELECT', ['""']);
|
||||
// * OK [CLOSED] Previous mailbox closed.
|
||||
// 3 NO [CANNOT] Invalid mailbox name: Name is empty
|
||||
} catch (\MailSo\Imap\Exceptions\NegativeResponseException $e) {
|
||||
if ('NO' === $e->GetResponseStatus()) {
|
||||
$this->oCurrentFolderInfo = null;
|
||||
}
|
||||
} catch (\MailSo\Imap\Exceptions\NegativeResponseException $oException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
$this->oCurrentFolderInfo = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -243,7 +291,7 @@ trait Folders
|
|||
$sCmd = 'EXPUNGE';
|
||||
$aArguments = array();
|
||||
|
||||
if ($oUidRange && \count($oUidRange) && $oUidRange->UID && $this->IsSupported('UIDPLUS')) {
|
||||
if ($oUidRange && \count($oUidRange) && $oUidRange->UID && $this->hasCapability('UIDPLUS')) {
|
||||
$sCmd = 'UID '.$sCmd;
|
||||
$aArguments = array((string) $oUidRange);
|
||||
}
|
||||
|
|
@ -272,9 +320,9 @@ trait Folders
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderSelect(string $sFolderName, bool $bReSelectSameFolders = false) : FolderInformation
|
||||
public function FolderSelect(string $sFolderName, bool $bForceReselect = false) : FolderInformation
|
||||
{
|
||||
return $this->selectOrExamineFolder($sFolderName, true, $bReSelectSameFolders);
|
||||
return $this->selectOrExamineFolder($sFolderName, true, $bForceReselect);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -288,9 +336,9 @@ trait Folders
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderExamine(string $sFolderName, bool $bReSelectSameFolders = false) : FolderInformation
|
||||
public function FolderExamine(string $sFolderName, bool $bForceReselect = false) : FolderInformation
|
||||
{
|
||||
return $this->selectOrExamineFolder($sFolderName, $this->__FORCE_SELECT_ON_EXAMINE__, $bReSelectSameFolders);
|
||||
return $this->selectOrExamineFolder($sFolderName, $this->Settings->force_select, $bForceReselect);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -302,18 +350,17 @@ trait Folders
|
|||
* REQUIRED IMAP4rev2 untagged responses: FLAGS, EXISTS, LIST
|
||||
* REQUIRED IMAP4rev2 OK untagged responses: PERMANENTFLAGS, UIDNEXT, UIDVALIDITY
|
||||
*/
|
||||
protected function selectOrExamineFolder(string $sFolderName, bool $bIsWritable, bool $bReSelectSameFolders) : FolderInformation
|
||||
protected function selectOrExamineFolder(string $sFolderName, bool $bIsWritable, bool $bForceReselect) : FolderInformation
|
||||
{
|
||||
if (!$bReSelectSameFolders
|
||||
if (!$bForceReselect
|
||||
&& $this->oCurrentFolderInfo
|
||||
&& $sFolderName === $this->oCurrentFolderInfo->FolderName
|
||||
&& $bIsWritable === $this->oCurrentFolderInfo->IsWritable
|
||||
&& $sFolderName === $this->oCurrentFolderInfo->FullName
|
||||
&& ($bIsWritable === $this->oCurrentFolderInfo->IsWritable || $this->oCurrentFolderInfo->IsWritable)
|
||||
) {
|
||||
return $this->oCurrentFolderInfo;
|
||||
}
|
||||
|
||||
if (!\strlen(\trim($sFolderName)))
|
||||
{
|
||||
if (!\strlen(\trim($sFolderName))) {
|
||||
throw new \InvalidArgumentException;
|
||||
}
|
||||
|
||||
|
|
@ -321,7 +368,7 @@ trait Folders
|
|||
|
||||
/*
|
||||
// RFC 5162
|
||||
if ($this->IsSupported('QRESYNC')) {
|
||||
if ($this->hasCapability('QRESYNC')) {
|
||||
$this->Enable(['QRESYNC', 'CONDSTORE']);
|
||||
- the last known UIDVALIDITY,
|
||||
- the last known modification sequence,
|
||||
|
|
@ -332,7 +379,7 @@ trait Folders
|
|||
}
|
||||
|
||||
// RFC 4551
|
||||
if ($this->IsSupported('CONDSTORE')) {
|
||||
if ($this->hasCapability('CONDSTORE')) {
|
||||
$aSelectParams[] = 'CONDSTORE';
|
||||
}
|
||||
|
||||
|
|
@ -365,9 +412,9 @@ trait Folders
|
|||
$oResult->PermanentFlags = \array_map('\\MailSo\\Base\\Utils::Utf7ModifiedToUtf8', $oResponse->OptionalResponse[1]);
|
||||
}
|
||||
} else if ('READ-ONLY' === $key) {
|
||||
// $oResult->IsWritable = false;
|
||||
$oResult->IsWritable = false;
|
||||
} else if ('READ-WRITE' === $key) {
|
||||
// $oResult->IsWritable = true;
|
||||
$oResult->IsWritable = true;
|
||||
} else if ('NOMODSEQ' === $key) {
|
||||
// https://datatracker.ietf.org/doc/html/rfc4551#section-3.1.2
|
||||
}
|
||||
|
|
@ -377,11 +424,20 @@ trait Folders
|
|||
else if (\count($oResponse->ResponseList) > 2
|
||||
&& 'FLAGS' === $oResponse->ResponseList[1]
|
||||
&& \is_array($oResponse->ResponseList[2])) {
|
||||
$oResult->Flags = \array_map('\\MailSo\\Base\\Utils::Utf7ModifiedToUtf8', $oResponse->ResponseList[2]);
|
||||
// These could be not permanent, so we don't use them
|
||||
// $oResult->Flags = \array_map('\\MailSo\\Base\\Utils::Utf7ModifiedToUtf8', $oResponse->ResponseList[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SELECT or EXAMINE command then UNSEEN is the message sequence number of the first unseen message.
|
||||
// IMAP4rev2 deprecated
|
||||
$oResult->UNSEEN = null;
|
||||
/*
|
||||
if ($this->hasCapability('ESEARCH')) {
|
||||
$oResult->UNSEEN = $this->MessageSimpleESearch('UNSEEN', ['COUNT'])['COUNT'];
|
||||
}
|
||||
*/
|
||||
$this->oCurrentFolderInfo = $oResult;
|
||||
|
||||
return $oResult;
|
||||
|
|
@ -392,7 +448,7 @@ trait Folders
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderList(string $sParentFolderName, string $sListPattern, bool $bIsSubscribeList = false, bool $bUseListStatus = false) : array
|
||||
public function FolderList(string $sParentFolderName, string $sListPattern, bool $bIsSubscribeList = false, bool $bUseListStatus = false) : FolderCollection
|
||||
{
|
||||
$sCmd = 'LIST';
|
||||
|
||||
|
|
@ -402,7 +458,7 @@ trait Folders
|
|||
if ($bIsSubscribeList) {
|
||||
// IMAP4rev2 deprecated
|
||||
$sCmd = 'LSUB';
|
||||
} else if ($this->IsSupported('LIST-EXTENDED')) {
|
||||
} else if ($this->hasCapability('LIST-EXTENDED')) {
|
||||
// RFC 5258
|
||||
$aReturnParams[] = 'SUBSCRIBED';
|
||||
// $aReturnParams[] = 'CHILDREN';
|
||||
|
|
@ -412,7 +468,7 @@ trait Folders
|
|||
// $aParameters[0] = '()';
|
||||
}
|
||||
// RFC 6154
|
||||
if ($this->IsSupported('SPECIAL-USE')) {
|
||||
if ($this->hasCapability('SPECIAL-USE')) {
|
||||
$aReturnParams[] = 'SPECIAL-USE';
|
||||
}
|
||||
}
|
||||
|
|
@ -422,32 +478,9 @@ trait Folders
|
|||
// $aParameters[] = $this->EscapeString(\strlen(\trim($sListPattern)) ? $sListPattern : '*');
|
||||
|
||||
// RFC 5819
|
||||
if ($bUseListStatus && !$bIsSubscribeList && $this->IsSupported('LIST-STATUS'))
|
||||
{
|
||||
$aL = array(
|
||||
FolderStatus::MESSAGES,
|
||||
FolderStatus::UNSEEN,
|
||||
FolderStatus::UIDNEXT
|
||||
);
|
||||
// RFC 4551
|
||||
if ($this->IsSupported('CONDSTORE')) {
|
||||
$aL[] = FolderStatus::HIGHESTMODSEQ;
|
||||
}
|
||||
// RFC 7889
|
||||
if ($this->IsSupported('APPENDLIMIT')) {
|
||||
$aL[] = FolderStatus::APPENDLIMIT;
|
||||
}
|
||||
// RFC 8474
|
||||
if ($this->IsSupported('OBJECTID')) {
|
||||
$aL[] = FolderStatus::MAILBOXID;
|
||||
/*
|
||||
} else if ($this->IsSupported('X-DOVECOT')) {
|
||||
$aL[] = 'X-GUID';
|
||||
*/
|
||||
}
|
||||
|
||||
if ($bUseListStatus && !$bIsSubscribeList && $this->hasCapability('LIST-STATUS')) {
|
||||
$aReturnParams[] = 'STATUS';
|
||||
$aReturnParams[] = $aL;
|
||||
$aReturnParams[] = $this->FolderStatusItems();
|
||||
}
|
||||
/*
|
||||
// RFC 5738
|
||||
|
|
@ -460,98 +493,94 @@ trait Folders
|
|||
$aParameters[] = $aReturnParams;
|
||||
}
|
||||
|
||||
/*
|
||||
$bPassthru = false;
|
||||
$aReturn = array();
|
||||
if ($bPassthru) {
|
||||
$this->SendRequest($sCmd, $aParameters);
|
||||
$this->streamResponse();
|
||||
return [];
|
||||
}
|
||||
*/
|
||||
|
||||
// RFC 5464
|
||||
$bMetadata = !$bIsSubscribeList && $this->IsSupported('METADATA');
|
||||
$aMetadata = null;
|
||||
if ($bMetadata) {
|
||||
// Dovecot supports fetching all METADATA at once
|
||||
$aMetadata = $this->getAllMetadata();
|
||||
}
|
||||
$bMetadata = !$bIsSubscribeList && $this->hasCapability('METADATA');
|
||||
// Dovecot supports fetching all METADATA at once
|
||||
$aMetadata = $bMetadata ? $this->getAllMetadata() : null;
|
||||
|
||||
$this->SendRequest($sCmd, $aParameters);
|
||||
if ($bPassthru) {
|
||||
$this->streamResponse();
|
||||
} else {
|
||||
$sDelimiter = '';
|
||||
$bInbox = false;
|
||||
foreach ($this->yieldUntaggedResponses() as $oResponse) {
|
||||
if ('STATUS' === $oResponse->StatusOrIndex && isset($oResponse->ResponseList[2])) {
|
||||
$sFullName = $this->toUTF8($oResponse->ResponseList[2]);
|
||||
if (!isset($aReturn[$sFullName])) {
|
||||
$aReturn[$sFullName] = new Folder($sFullName);
|
||||
}
|
||||
$aReturn[$sFullName]->setStatusFromResponse($oResponse);
|
||||
}
|
||||
else if ($sCmd === $oResponse->StatusOrIndex && 5 === \count($oResponse->ResponseList)) {
|
||||
try
|
||||
{
|
||||
$sFullName = $this->toUTF8($oResponse->ResponseList[4]);
|
||||
|
||||
/**
|
||||
* $oResponse->ResponseList[0] = *
|
||||
* $oResponse->ResponseList[1] = LIST (all) | LSUB (subscribed)
|
||||
* $oResponse->ResponseList[2] = Flags
|
||||
* $oResponse->ResponseList[3] = Delimiter
|
||||
* $oResponse->ResponseList[4] = FullName
|
||||
*/
|
||||
if (!isset($aReturn[$sFullName])) {
|
||||
$oFolder = new Folder($sFullName,
|
||||
$oResponse->ResponseList[3], $oResponse->ResponseList[2]);
|
||||
$aReturn[$sFullName] = $oFolder;
|
||||
} else {
|
||||
$oFolder = $aReturn[$sFullName];
|
||||
$oFolder->setDelimiter($oResponse->ResponseList[3]);
|
||||
$oFolder->setFlags($oResponse->ResponseList[2]);
|
||||
}
|
||||
|
||||
if ($oFolder->IsInbox()) {
|
||||
$bInbox = true;
|
||||
}
|
||||
|
||||
if (!$sDelimiter) {
|
||||
$sDelimiter = $oFolder->Delimiter();
|
||||
}
|
||||
|
||||
if (isset($aMetadata[$oResponse->ResponseList[4]])) {
|
||||
$oFolder->SetAllMetadata($aMetadata[$oResponse->ResponseList[4]]);
|
||||
}
|
||||
|
||||
$aReturn[$sFullName] = $oFolder;
|
||||
}
|
||||
catch (\InvalidArgumentException $oException)
|
||||
{
|
||||
$this->writeLogException($oException, \LOG_WARNING, false);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$this->writeLogException($oException, \LOG_WARNING, false);
|
||||
}
|
||||
$aFolders = array();
|
||||
$oFolderCollection = new FolderCollection;
|
||||
$sDelimiter = '';
|
||||
$bInbox = false;
|
||||
foreach ($this->yieldUntaggedResponses() as $oResponse) {
|
||||
if ('STATUS' === $oResponse->StatusOrIndex && isset($oResponse->ResponseList[2])) {
|
||||
$sFullName = $this->toUTF8($oResponse->ResponseList[2]);
|
||||
if (!isset($oFolderCollection[$sFullName])) {
|
||||
$oFolderCollection[$sFullName] = new Folder($sFullName);
|
||||
}
|
||||
$oFolderCollection[$sFullName]->setStatusFromResponse($oResponse);
|
||||
$oFolderCollection[$sFullName]->generateETag($this);
|
||||
}
|
||||
else if ($sCmd === $oResponse->StatusOrIndex && 5 === \count($oResponse->ResponseList)) {
|
||||
try
|
||||
{
|
||||
$sFullName = $this->toUTF8($oResponse->ResponseList[4]);
|
||||
|
||||
if (!$bInbox && !$sParentFolderName && !isset($aReturn['INBOX'])) {
|
||||
$aReturn['INBOX'] = new Folder('INBOX', $sDelimiter);
|
||||
/**
|
||||
* $oResponse->ResponseList[0] = *
|
||||
* $oResponse->ResponseList[1] = LIST (all) | LSUB (subscribed)
|
||||
* $oResponse->ResponseList[2] = Attribute flags
|
||||
* $oResponse->ResponseList[3] = Delimiter
|
||||
* $oResponse->ResponseList[4] = FullName
|
||||
*/
|
||||
if (isset($oFolderCollection[$sFullName])) {
|
||||
$oFolder = $oFolderCollection[$sFullName];
|
||||
$oFolder->setDelimiter($oResponse->ResponseList[3]);
|
||||
$oFolder->setAttributes($oResponse->ResponseList[2]);
|
||||
} else {
|
||||
$oFolder = new Folder($sFullName, $oResponse->ResponseList[3], $oResponse->ResponseList[2]);
|
||||
$oFolderCollection[$sFullName] = $oFolder;
|
||||
}
|
||||
|
||||
$bInbox = $bInbox || $oFolder->IsInbox();
|
||||
|
||||
if (!$sDelimiter) {
|
||||
$sDelimiter = $oFolder->Delimiter();
|
||||
}
|
||||
|
||||
if (isset($aMetadata[$oResponse->ResponseList[4]])) {
|
||||
$oFolder->SetAllMetadata($aMetadata[$oResponse->ResponseList[4]]);
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$this->writeLogException($oException, \LOG_WARNING, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// $iOptimizationLimit = $this->Settings->folder_list_limit;
|
||||
// $oFolderCollection->Optimized = 10 < $iOptimizationLimit && $oFolderCollection->count() > $iOptimizationLimit;
|
||||
|
||||
// RFC 5464
|
||||
if ($bMetadata && !$aMetadata /*&& 50 < \count($aReturn)*/) {
|
||||
foreach ($aReturn as $oFolder) {
|
||||
// if (2 > \substr_count($oFolder->FullName(), $oFolder->Delimiter()))
|
||||
if ($bMetadata && !$aMetadata /*&& 50 < $oFolderCollection->count()*/) {
|
||||
foreach ($oFolderCollection as $oFolder) {
|
||||
// if (2 > \substr_count($oFolder->FullName, $oFolder->Delimiter()))
|
||||
try {
|
||||
$oFolder->SetAllMetadata(
|
||||
$this->getMetadata($oFolder->FullName(), ['/shared', '/private'], ['DEPTH'=>'infinity'])
|
||||
$this->getMetadata($oFolder->FullName, ['/shared', '/private'], ['DEPTH'=>'infinity'])
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Throwable $oException) {
|
||||
// Ignore error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $aReturn;
|
||||
if (!$bInbox && !$sParentFolderName && !isset($oFolderCollection['INBOX'])) {
|
||||
$oFolderCollection['INBOX'] = new Folder('INBOX', $sDelimiter);
|
||||
}
|
||||
|
||||
return $oFolderCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -559,7 +588,7 @@ trait Folders
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderSubscribeList(string $sParentFolderName, string $sListPattern) : array
|
||||
public function FolderSubscribeList(string $sParentFolderName, string $sListPattern) : FolderCollection
|
||||
{
|
||||
return $this->FolderList($sParentFolderName, $sListPattern, true);
|
||||
}
|
||||
|
|
@ -569,7 +598,7 @@ trait Folders
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function FolderStatusList(string $sParentFolderName, string $sListPattern) : array
|
||||
public function FolderStatusList(string $sParentFolderName, string $sListPattern) : FolderCollection
|
||||
{
|
||||
return $this->FolderList($sParentFolderName, $sListPattern, false, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ use MailSo\Imap\FetchResponse;
|
|||
use MailSo\Imap\Enumerations\FetchType;
|
||||
use MailSo\Imap\ResponseCollection;
|
||||
use MailSo\Imap\SequenceSet;
|
||||
use MailSo\Imap\Enumerations\MessageFlag;
|
||||
use MailSo\Imap\Enumerations\ResponseType;
|
||||
use MailSo\Imap\Enumerations\StoreAction;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
|
|
@ -30,11 +32,10 @@ trait Messages
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function Fetch(array $aInputFetchItems, string $sIndexRange, bool $bIndexIsUid) : array
|
||||
public function FetchIterate(array $aInputFetchItems, string $sIndexRange, bool $bIndexIsUid) : iterable
|
||||
{
|
||||
if (!\strlen(\trim($sIndexRange)))
|
||||
{
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR, true);
|
||||
if (!\strlen(\trim($sIndexRange))) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
||||
}
|
||||
|
||||
$aReturn = array();
|
||||
|
|
@ -44,8 +45,7 @@ trait Messages
|
|||
FetchType::UID,
|
||||
FetchType::RFC822_SIZE
|
||||
);
|
||||
foreach ($aInputFetchItems as $mFetchKey)
|
||||
{
|
||||
foreach ($aInputFetchItems as $mFetchKey) {
|
||||
switch ($mFetchKey)
|
||||
{
|
||||
case FetchType::UID:
|
||||
|
|
@ -77,20 +77,20 @@ trait Messages
|
|||
break;
|
||||
}
|
||||
}
|
||||
if ($this->IsSupported('OBJECTID')) {
|
||||
if ($this->hasCapability('OBJECTID')) {
|
||||
$aFetchItems[] = FetchType::EMAILID;
|
||||
$aFetchItems[] = FetchType::THREADID;
|
||||
} else if ($this->IsSupported('X-GM-EXT-1')) {
|
||||
} else if ($this->hasCapability('X-GM-EXT-1')) {
|
||||
// https://developers.google.com/gmail/imap/imap-extensions
|
||||
$aFetchItems[] = 'X-GM-MSGID';
|
||||
$aFetchItems[] = 'X-GM-THRID';
|
||||
/*
|
||||
} else if ($this->IsSupported('X-DOVECOT')) {
|
||||
} else if ($this->hasCapability('X-DOVECOT')) {
|
||||
$aFetchItems[] = 'X-GUID';
|
||||
*/
|
||||
}
|
||||
/*
|
||||
if ($this->IsSupported('X-GM-EXT-1') && \in_array(FetchType::FLAGS, $aFetchItems)) {
|
||||
if ($this->hasCapability('X-GM-EXT-1') && \in_array(FetchType::FLAGS, $aFetchItems)) {
|
||||
$aFetchItems[] = 'X-GM-LABELS';
|
||||
}
|
||||
*/
|
||||
|
|
@ -112,31 +112,42 @@ trait Messages
|
|||
foreach ($this->yieldUntaggedResponses() as $oResponse) {
|
||||
if (FetchResponse::isValidImapResponse($oResponse)) {
|
||||
if (FetchResponse::hasUidAndSize($oResponse)) {
|
||||
$aReturn[] = new FetchResponse($oResponse);
|
||||
} else if ($this->oLogger) {
|
||||
$this->oLogger->Write('Skipped Imap Response! ['.$oResponse.']', \LOG_NOTICE);
|
||||
yield new FetchResponse($oResponse);
|
||||
} else {
|
||||
$this->logWrite('Skipped Imap Response! ['.$oResponse.']', \LOG_NOTICE);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$this->aFetchCallbacks = array();
|
||||
}
|
||||
}
|
||||
|
||||
public function Fetch(array $aInputFetchItems, string $sIndexRange, bool $bIndexIsUid) : array
|
||||
{
|
||||
$aReturn = array();
|
||||
foreach ($this->FetchIterate($aInputFetchItems, $sIndexRange, $bIndexIsUid) as $oFetchResponse) {
|
||||
$aReturn[] = $oFetchResponse;
|
||||
}
|
||||
return $aReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends message to specified folder
|
||||
*
|
||||
* @param resource $rMessageAppendStream
|
||||
* @param resource $rMessageStream
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function MessageAppendStream(string $sFolderName, $rMessageAppendStream, int $iStreamSize, array $aFlagsList = null, int $iDateTime = 0) : ?int
|
||||
public function MessageAppendStream(string $sFolderName, $rMessageStream, int $iStreamSize, array $aFlagsList = null, int $iDateTime = 0) : ?int
|
||||
{
|
||||
if (!\is_resource($rMessageStream) || !\strlen($sFolderName) || 1 > $iStreamSize) {
|
||||
throw new \InvalidArgumentException;
|
||||
}
|
||||
|
||||
$aParams = array(
|
||||
$this->EscapeFolderName($sFolderName),
|
||||
$aFlagsList
|
||||
|
|
@ -147,7 +158,7 @@ trait Messages
|
|||
|
||||
/*
|
||||
// RFC 3516 || RFC 6855 section-4
|
||||
if ($this->IsSupported('BINARY') || $this->IsSupported('UTF8=ACCEPT')) {
|
||||
if ($this->hasCapability('BINARY') || $this->hasCapability('UTF8=ACCEPT')) {
|
||||
$aParams[] = '~{'.$iStreamSize.'}';
|
||||
}
|
||||
*/
|
||||
|
|
@ -155,14 +166,14 @@ trait Messages
|
|||
|
||||
$this->SendRequestGetResponse('APPEND', $aParams);
|
||||
|
||||
return $this->writeMessageStream($rMessageAppendStream);
|
||||
return $this->writeMessageStream($rMessageStream);
|
||||
}
|
||||
|
||||
private function writeMessageStream($rMessageStream) : ?int
|
||||
{
|
||||
$this->writeLog('Write to connection stream', \LOG_INFO);
|
||||
|
||||
\MailSo\Base\Utils::MultipleStreamWriter($rMessageStream, array($this->ConnectionResource()));
|
||||
\MailSo\Base\Utils::WriteStream($rMessageStream, $this->ConnectionResource());
|
||||
|
||||
$this->sendRaw('');
|
||||
$oResponses = $this->getResponse();
|
||||
|
|
@ -196,12 +207,14 @@ trait Messages
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function MessageCopy(string $sToFolder, SequenceSet $oRange) : ResponseCollection
|
||||
public function MessageCopy(string $sFromFolder, string $sToFolder, SequenceSet $oRange) : ResponseCollection
|
||||
{
|
||||
if (!\count($oRange)) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR, true);
|
||||
if (!$sFromFolder || !$sToFolder || !\count($oRange)) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
||||
}
|
||||
|
||||
$this->FolderSelect($sFromFolder);
|
||||
|
||||
return $this->SendRequestGetResponse(
|
||||
$oRange->UID ? 'UID COPY' : 'COPY',
|
||||
array((string) $oRange, $this->EscapeFolderName($sToFolder))
|
||||
|
|
@ -214,22 +227,48 @@ trait Messages
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function MessageMove(string $sToFolder, SequenceSet $oRange) : ResponseCollection
|
||||
public function MessageMove(string $sFromFolder, string $sToFolder, SequenceSet $oRange) : void
|
||||
{
|
||||
if (!\count($oRange)) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR, true);
|
||||
if (!$sFromFolder || !$sToFolder || !\count($oRange)) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
||||
}
|
||||
|
||||
if (!$this->IsSupported('MOVE')) {
|
||||
$this->writeLogException(
|
||||
new \MailSo\RuntimeException('Move is not supported'),
|
||||
\LOG_ERR, true);
|
||||
if ($this->hasCapability('MOVE')) {
|
||||
$this->FolderSelect($sFromFolder);
|
||||
$this->SendRequestGetResponse(
|
||||
$oRange->UID ? 'UID MOVE' : 'MOVE',
|
||||
array((string) $oRange, $this->EscapeFolderName($sToFolder))
|
||||
);
|
||||
} else {
|
||||
$this->MessageCopy($sFromFolder, $sToFolder, $oRange);
|
||||
$this->MessageDelete($sFromFolder, $oRange, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function MessageDelete(string $sFolder, SequenceSet $oRange, bool $bExpungeAll = false) : void
|
||||
{
|
||||
if (!$sFolder || !\count($oRange)) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
||||
}
|
||||
|
||||
return $this->SendRequestGetResponse(
|
||||
$oRange->UID ? 'UID MOVE' : 'MOVE',
|
||||
array((string) $oRange, $this->EscapeFolderName($sToFolder))
|
||||
$this->FolderSelect($sFolder);
|
||||
|
||||
$this->MessageStoreFlag($oRange,
|
||||
array(MessageFlag::DELETED),
|
||||
StoreAction::ADD_FLAGS_SILENT
|
||||
);
|
||||
|
||||
if ($bExpungeAll && $this->Settings->expunge_all_on_delete) {
|
||||
$this->FolderExpunge();
|
||||
} else {
|
||||
$this->FolderExpunge($oRange);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -246,14 +285,14 @@ trait Messages
|
|||
*/
|
||||
public function MessageReplaceStream(string $sFolderName, int $iUid, $rMessageStream, int $iStreamSize, array $aFlagsList = null, int $iDateTime = 0) : ?int
|
||||
{
|
||||
if (1 > $iUid || !$this->IsSupported('REPLACE')) {
|
||||
if (1 > $iUid || !$this->hasCapability('REPLACE')) {
|
||||
$this->FolderSelect($sFolderName);
|
||||
$iNewUid = $this->MessageAppendStream($sFolderName, $rMessageStream, $iStreamSize, $aFlagsList, $iDateTime);
|
||||
if ($iUid) {
|
||||
$oRange = new SequenceSet([$iUid]);
|
||||
$this->MessageStoreFlag($oRange,
|
||||
array(\MailSo\Imap\Enumerations\MessageFlag::DELETED),
|
||||
\MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT
|
||||
array(MessageFlag::DELETED),
|
||||
StoreAction::ADD_FLAGS_SILENT
|
||||
);
|
||||
$this->FolderExpunge($oRange);
|
||||
}
|
||||
|
|
@ -271,7 +310,7 @@ trait Messages
|
|||
|
||||
/*
|
||||
// RFC 3516 || RFC 6855 section-4
|
||||
if ($this->IsSupported('BINARY') || $this->IsSupported('UTF8=ACCEPT')) {
|
||||
if ($this->hasCapability('BINARY') || $this->hasCapability('UTF8=ACCEPT')) {
|
||||
$aParams[] = '~{'.$iStreamSize.'}';
|
||||
}
|
||||
*/
|
||||
|
|
@ -287,6 +326,7 @@ trait Messages
|
|||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
* $sStoreAction = \MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT
|
||||
*/
|
||||
public function MessageStoreFlag(SequenceSet $oRange, array $aInputStoreItems, string $sStoreAction) : ?ResponseCollection
|
||||
{
|
||||
|
|
|
|||
|
|
@ -96,23 +96,23 @@ trait Metadata
|
|||
|
||||
public function ServerGetMetadata(array $aEntries, array $aOptions = []) : array
|
||||
{
|
||||
return $this->IsSupported('METADATA-SERVER')
|
||||
return $this->hasCapability('METADATA-SERVER')
|
||||
? $this->getMetadata('', $aEntries, $aOptions)
|
||||
: [];
|
||||
}
|
||||
|
||||
public function FolderGetMetadata(string $sFolderName, array $aEntries, array $aOptions = []) : array
|
||||
{
|
||||
return $this->IsSupported('METADATA')
|
||||
return $this->hasCapability('METADATA')
|
||||
? $this->getMetadata($sFolderName, $aEntries, $aOptions)
|
||||
: [];
|
||||
}
|
||||
|
||||
public function FolderSetMetadata(string $sFolderName, array $aEntries) : void
|
||||
{
|
||||
if ($this->IsSupported('METADATA')) {
|
||||
if ($this->hasCapability('METADATA')) {
|
||||
if (!$aEntries) {
|
||||
throw new \MailSo\Base\Exceptions\InvalidArgumentException("Wrong argument for SETMETADATA command");
|
||||
throw new \InvalidArgumentException('Wrong argument for SETMETADATA command');
|
||||
}
|
||||
|
||||
$arguments = [$this->EscapeFolderName($sFolderName)];
|
||||
|
|
@ -126,4 +126,8 @@ trait Metadata
|
|||
}
|
||||
}
|
||||
|
||||
public function FolderRemoveMetadata($sFolderName, array $aEntries) : void
|
||||
{
|
||||
$this->FolderSetMetadata($sFolderName, \array_fill_keys(\array_keys($aEntries), null));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ trait Quota
|
|||
// * QUOTA "User quota" (STORAGE 1284645 2097152)\r\n
|
||||
private function getQuota(bool $root, string $sFolderName) : ?array
|
||||
{
|
||||
if (!$this->IsSupported('QUOTA')) {
|
||||
if (!$this->hasCapability('QUOTA')) {
|
||||
return null;
|
||||
}
|
||||
$oResponseCollection = $this->SendRequest(($root?'GETQUOTAROOT':'GETQUOTA') . " {$this->EscapeFolderName($sFolderName)}");
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2014 Usenko Timur
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo\Imap\Enumerations;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Imap
|
||||
* @subpackage Enumerations
|
||||
*/
|
||||
abstract class FolderResponseStatus
|
||||
{
|
||||
// RFC 3501
|
||||
const MESSAGES = 'MESSAGES';
|
||||
// const RECENT = 'RECENT'; // IMAP4rev2 deprecated
|
||||
const UIDNEXT = 'UIDNEXT';
|
||||
const UIDVALIDITY = 'UIDVALIDITY';
|
||||
const UNSEEN = 'UNSEEN';
|
||||
// RFC 4551
|
||||
const HIGHESTMODSEQ = 'HIGHESTMODSEQ';
|
||||
// RFC 7889
|
||||
const APPENDLIMIT = 'APPENDLIMIT';
|
||||
// RFC 8474
|
||||
const MAILBOXID = 'MAILBOXID';
|
||||
// RFC 9051 IMAP4rev2
|
||||
const SIZE = 'SIZE';
|
||||
}
|
||||
|
|
@ -30,4 +30,6 @@ abstract class FolderStatus
|
|||
const APPENDLIMIT = 'APPENDLIMIT';
|
||||
// RFC 8474
|
||||
const MAILBOXID = 'MAILBOXID';
|
||||
// RFC 9051 IMAP4rev2
|
||||
const SIZE = 'SIZE';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ namespace MailSo\Imap\Exceptions;
|
|||
*/
|
||||
class ResponseException extends \MailSo\RuntimeException
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $oResponses;
|
||||
|
||||
public function __construct(?\MailSo\Imap\ResponseCollection $oResponses = null, string $sMessage = '', int $iCode = 0, ?\Throwable $oPrevious = null)
|
||||
|
|
|
|||
|
|
@ -17,26 +17,18 @@ namespace MailSo\Imap;
|
|||
*/
|
||||
class FetchResponse
|
||||
{
|
||||
/**
|
||||
* @var \MailSo\Imap\Response
|
||||
*/
|
||||
private $oImapResponse;
|
||||
public Response $oImapResponse;
|
||||
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private $aEnvelopeCache;
|
||||
private ?array $aEnvelopeCache = null;
|
||||
|
||||
function __construct(Response $oImapResponse)
|
||||
{
|
||||
$this->oImapResponse = $oImapResponse;
|
||||
$this->aEnvelopeCache = null;
|
||||
}
|
||||
|
||||
public function GetEnvelope(bool $bForce = false) : ?array
|
||||
{
|
||||
if (null === $this->aEnvelopeCache || $bForce)
|
||||
{
|
||||
if (null === $this->aEnvelopeCache || $bForce) {
|
||||
$this->aEnvelopeCache = $this->GetFetchValue(Enumerations\FetchType::ENVELOPE);
|
||||
}
|
||||
return $this->aEnvelopeCache;
|
||||
|
|
@ -54,13 +46,10 @@ class FetchResponse
|
|||
{
|
||||
$oResult = null;
|
||||
$aEmails = $this->GetFetchEnvelopeValue($iIndex);
|
||||
if (\is_array($aEmails) && \count($aEmails))
|
||||
{
|
||||
if (\is_array($aEmails) && \count($aEmails)) {
|
||||
$oResult = new \MailSo\Mime\EmailCollection;
|
||||
foreach ($aEmails as $aEmailItem)
|
||||
{
|
||||
if (\is_array($aEmailItem) && 4 === \count($aEmailItem))
|
||||
{
|
||||
foreach ($aEmails as $aEmailItem) {
|
||||
if (\is_array($aEmailItem) && 4 === \count($aEmailItem)) {
|
||||
$sDisplayName = \MailSo\Base\Utils::DecodeHeaderValue(
|
||||
self::findEnvelopeIndex($aEmailItem, 0, ''), $sParentCharset);
|
||||
|
||||
|
|
@ -70,8 +59,7 @@ class FetchResponse
|
|||
$sLocalPart = self::findEnvelopeIndex($aEmailItem, 2, '');
|
||||
$sDomainPart = self::findEnvelopeIndex($aEmailItem, 3, '');
|
||||
|
||||
if (\strlen($sLocalPart) && \strlen($sDomainPart))
|
||||
{
|
||||
if (\strlen($sLocalPart) && \strlen($sDomainPart)) {
|
||||
$oResult->append(
|
||||
new \MailSo\Mime\Email($sLocalPart.'@'.$sDomainPart, $sDisplayName)
|
||||
);
|
||||
|
|
@ -122,18 +110,15 @@ class FetchResponse
|
|||
{
|
||||
$bNextIsValue = false;
|
||||
|
||||
if (isset($this->oImapResponse->ResponseList[3]) && \is_array($this->oImapResponse->ResponseList[3]))
|
||||
{
|
||||
foreach ($this->oImapResponse->ResponseList[3] as $mItem)
|
||||
{
|
||||
if ($bNextIsValue)
|
||||
{
|
||||
if (isset($this->oImapResponse->ResponseList[3]) && \is_array($this->oImapResponse->ResponseList[3])) {
|
||||
foreach ($this->oImapResponse->ResponseList[3] as $mItem) {
|
||||
if ($bNextIsValue) {
|
||||
return (string) $mItem;
|
||||
}
|
||||
|
||||
if (\is_string($mItem) && (
|
||||
$mItem === 'BODY[HEADER]' ||
|
||||
0 === \strpos($mItem, 'BODY[HEADER.FIELDS') ||
|
||||
\str_starts_with($mItem, 'BODY[HEADER.FIELDS') ||
|
||||
$mItem === 'BODY[MIME]'))
|
||||
{
|
||||
$bNextIsValue = true;
|
||||
|
|
|
|||
|
|
@ -11,58 +11,54 @@
|
|||
|
||||
namespace MailSo\Imap;
|
||||
|
||||
use MailSo\Imap\Enumerations\MetadataKeys;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Imap
|
||||
*/
|
||||
class Folder
|
||||
class Folder implements \JsonSerializable
|
||||
{
|
||||
// RFC5258 Response data STATUS items when using LIST-EXTENDED
|
||||
use Traits\Status;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sFullName;
|
||||
private ?string $sDelimiter;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sDelimiter;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aFlagsLowerCase;
|
||||
private array $aAttributes;
|
||||
|
||||
/**
|
||||
* RFC 5464
|
||||
*/
|
||||
private $aMetadata = array();
|
||||
private array $aMetadata = array();
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
function __construct(string $sFullName, string $sDelimiter = null, array $aFlags = array())
|
||||
function __construct(string $sFullName, string $sDelimiter = null, array $aAttributes = array())
|
||||
{
|
||||
if (!\strlen($sFullName)) {
|
||||
throw new \InvalidArgumentException;
|
||||
}
|
||||
$this->sFullName = $sFullName;
|
||||
$this->FullName = $sFullName;
|
||||
$this->setDelimiter($sDelimiter);
|
||||
$this->setFlags($aFlags);
|
||||
$this->setAttributes($aAttributes);
|
||||
/*
|
||||
// RFC 5738
|
||||
if (\in_array('\\noutf8', $this->aFlagsLowerCase)) {
|
||||
if (\in_array('\\noutf8', $this->aAttributes)) {
|
||||
}
|
||||
if (\in_array('\\utf8only', $this->aFlagsLowerCase)) {
|
||||
if (\in_array('\\utf8only', $this->aAttributes)) {
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public function setFlags(array $aFlags) : void
|
||||
public function setAttributes(array $aAttributes) : void
|
||||
{
|
||||
$this->aFlagsLowerCase = \array_map('strtolower', $aFlags);
|
||||
$this->aAttributes = \array_map('mb_strtolower', $aAttributes);
|
||||
}
|
||||
|
||||
public function setSubscribed() : void
|
||||
{
|
||||
$this->aAttributes = \array_unique(\array_merge($this->aAttributes, ['\\subscribed']));
|
||||
}
|
||||
|
||||
public function setDelimiter(?string $sDelimiter) : void
|
||||
|
|
@ -72,7 +68,7 @@ class Folder
|
|||
|
||||
public function Name() : string
|
||||
{
|
||||
$sNameRaw = $this->sFullName;
|
||||
$sNameRaw = $this->FullName;
|
||||
if ($this->sDelimiter) {
|
||||
$aNames = \explode($this->sDelimiter, $sNameRaw);
|
||||
return \end($aNames);
|
||||
|
|
@ -80,29 +76,25 @@ class Folder
|
|||
return $sNameRaw;
|
||||
}
|
||||
|
||||
public function FullName() : string
|
||||
{
|
||||
return $this->sFullName;
|
||||
}
|
||||
|
||||
public function Delimiter() : ?string
|
||||
{
|
||||
return $this->sDelimiter;
|
||||
}
|
||||
|
||||
public function FlagsLowerCase() : array
|
||||
public function Selectable() : bool
|
||||
{
|
||||
return $this->aFlagsLowerCase;
|
||||
return !\in_array('\\noselect', $this->aAttributes)
|
||||
&& !\in_array('\\nonexistent', $this->aAttributes);
|
||||
}
|
||||
|
||||
public function IsSelectable() : bool
|
||||
public function IsSubscribed() : bool
|
||||
{
|
||||
return !\in_array('\\noselect', $this->aFlagsLowerCase) && !\in_array('\\nonexistent', $this->aFlagsLowerCase);
|
||||
return \in_array('\\subscribed', $this->aAttributes);
|
||||
}
|
||||
|
||||
public function IsInbox() : bool
|
||||
{
|
||||
return 'INBOX' === \strtoupper($this->sFullName) || \in_array('\\inbox', $this->aFlagsLowerCase);
|
||||
return 'INBOX' === \strtoupper($this->FullName) || \in_array('\\inbox', $this->aAttributes);
|
||||
}
|
||||
|
||||
public function SetMetadata(string $sName, string $sData) : void
|
||||
|
|
@ -124,4 +116,72 @@ class Folder
|
|||
{
|
||||
return $this->aMetadata;
|
||||
}
|
||||
|
||||
// JMAP RFC 8621
|
||||
public function Role() : ?string
|
||||
{
|
||||
$role = \strtolower($this->GetMetadata(MetadataKeys::SPECIALUSE) ?: '');
|
||||
if (!$role) {
|
||||
$match = \array_intersect([
|
||||
'\\inbox',
|
||||
'\\all', // '\\allmail'
|
||||
'\\archive',
|
||||
'\\drafts',
|
||||
'\\flagged', // '\\starred'
|
||||
'\\important',
|
||||
'\\junk', // '\\spam'
|
||||
'\\sent', // '\\sentmail'
|
||||
'\\trash', // '\\bin'
|
||||
], $this->aAttributes);
|
||||
if ($match) {
|
||||
$role = \array_shift($match);
|
||||
}
|
||||
if (!$role && 'INBOX' === \strtoupper($this->FullName)) {
|
||||
return 'inbox';
|
||||
}
|
||||
}
|
||||
return $role ? \ltrim($role, '\\') : null;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
/*
|
||||
if ($this->ImapClient->hasCapability('ACL') || $this->ImapClient->CapabilityValue('RIGHTS')) {
|
||||
// MailSo\Imap\Responses\ACL
|
||||
$rights = $this->ImapClient->FolderMyRights($this->FullName);
|
||||
}
|
||||
*/
|
||||
$result = array(
|
||||
'@Object' => 'Object/Folder',
|
||||
'name' => $this->Name(),
|
||||
'fullName' => $this->FullName,
|
||||
'delimiter' => (string) $this->sDelimiter,
|
||||
'attributes' => $this->aAttributes,
|
||||
'metadata' => $this->aMetadata,
|
||||
'uidNext' => $this->UIDNEXT,
|
||||
// https://datatracker.ietf.org/doc/html/rfc8621#section-2
|
||||
'totalEmails' => $this->MESSAGES,
|
||||
'unreadEmails' => $this->UNSEEN,
|
||||
'id' => $this->MAILBOXID,
|
||||
'role' => $this->Role()
|
||||
/*
|
||||
'myRights' => [
|
||||
'mayReadItems' => !$rights || ($rights->hasRight('l') && $rights->hasRight('r')),
|
||||
'mayAddItems' => !$rights || $rights->hasRight('i'),
|
||||
'mayRemoveItems' => !$rights || ($rights->hasRight('t') && $rights->hasRight('e')),
|
||||
'maySetSeen' => !$rights || $rights->hasRight('s'),
|
||||
'maySetKeywords' => !$rights || $rights->hasRight('w'),
|
||||
'mayCreateChild' => !$rights || $rights->hasRight('k'),
|
||||
'mayRename' => !$rights || $rights->hasRight('x'),
|
||||
'mayDelete' => !$rights || $rights->hasRight('x'),
|
||||
'maySubmit' => !$rights || $rights->hasRight('p')
|
||||
]
|
||||
*/
|
||||
);
|
||||
if ($this->etag) {
|
||||
$result['etag'] = $this->etag;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo\Mail;
|
||||
namespace MailSo\Imap;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
|
|
@ -17,12 +17,7 @@ namespace MailSo\Mail;
|
|||
*/
|
||||
class FolderCollection extends \MailSo\Base\Collection
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $Optimized = false;
|
||||
|
||||
public $TotalCount = 0;
|
||||
// public bool $Optimized = false;
|
||||
|
||||
public function append($oFolder, bool $bToTop = false) : void
|
||||
{
|
||||
|
|
@ -30,9 +25,13 @@ class FolderCollection extends \MailSo\Base\Collection
|
|||
parent::append($oFolder, $bToTop);
|
||||
}
|
||||
|
||||
public function FindDelimiter() : string
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
$oFolder = $this['INBOX'] ?? $this[0] ?? null;
|
||||
return $oFolder ? $oFolder->Delimiter() : '/';
|
||||
return array(
|
||||
'@Object' => 'Collection/FolderCollection',
|
||||
'@Collection' => \array_values($this->getArrayCopy()),
|
||||
// 'optimized' => $this->Optimized
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,45 +19,32 @@ class FolderInformation implements \JsonSerializable
|
|||
{
|
||||
use Traits\Status;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $FolderName;
|
||||
public bool $IsWritable;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $IsWritable;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* Message flags
|
||||
* https://www.rfc-editor.org/rfc/rfc3501#section-7.2.6
|
||||
* These could be not permanent so we don't use them
|
||||
*/
|
||||
public $Flags = array();
|
||||
public array $Flags = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* NOTE: Empty when FolderExamine is used
|
||||
* https://www.rfc-editor.org/rfc/rfc3501#page-64
|
||||
*/
|
||||
public $PermanentFlags = array();
|
||||
public array $PermanentFlags = array();
|
||||
|
||||
/**
|
||||
* https://datatracker.ietf.org/doc/html/rfc3501#section-7.3.1
|
||||
* @var int
|
||||
*/
|
||||
public $Exists = null;
|
||||
|
||||
function __construct(string $sFolderName, bool $bIsWritable)
|
||||
function __construct(string $sFullName, bool $bIsWritable)
|
||||
{
|
||||
$this->FolderName = $sFolderName;
|
||||
$this->FullName = $sFullName;
|
||||
$this->IsWritable = $bIsWritable;
|
||||
}
|
||||
|
||||
public function IsFlagSupported(string $sFlag) : bool
|
||||
{
|
||||
return \in_array('\\*', $this->PermanentFlags) ||
|
||||
\in_array($sFlag, $this->PermanentFlags) ||
|
||||
\in_array($sFlag, $this->Flags);
|
||||
return \in_array('\\*', $this->PermanentFlags)
|
||||
|| \in_array($sFlag, $this->PermanentFlags);
|
||||
// || \in_array($sFlag, $this->Flags);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
|
|
@ -65,24 +52,33 @@ class FolderInformation implements \JsonSerializable
|
|||
{
|
||||
$result = array(
|
||||
'id' => $this->MAILBOXID,
|
||||
'Name' => $this->FolderName,
|
||||
'Flags' => $this->Flags,
|
||||
'PermanentFlags' => $this->PermanentFlags,
|
||||
'UidNext' => $this->UIDNEXT,
|
||||
'UidValidity' => $this->UIDVALIDITY
|
||||
'name' => $this->FullName,
|
||||
'uidNext' => $this->UIDNEXT,
|
||||
'uidValidity' => $this->UIDVALIDITY
|
||||
);
|
||||
if (isset($this->MESSAGES)) {
|
||||
$result['totalEmails'] = $this->MESSAGES;
|
||||
$result['unreadEmails'] = $this->UNSEEN;
|
||||
}
|
||||
if (isset($this->HIGHESTMODSEQ)) {
|
||||
$result['Highestmodseq'] = $this->HIGHESTMODSEQ;
|
||||
$result['highestModSeq'] = $this->HIGHESTMODSEQ;
|
||||
}
|
||||
if (isset($this->APPENDLIMIT)) {
|
||||
$result['Appendlimit'] = $this->APPENDLIMIT;
|
||||
$result['appendLimit'] = $this->APPENDLIMIT;
|
||||
}
|
||||
if (isset($this->SIZE)) {
|
||||
$result['Size'] = $this->SIZE;
|
||||
$result['size'] = $this->SIZE;
|
||||
}
|
||||
if ($this->etag) {
|
||||
$result['etag'] = $this->etag;
|
||||
}
|
||||
/*
|
||||
if ($this->Flags) {
|
||||
$result['flags'] = $this->Flags;
|
||||
}
|
||||
*/
|
||||
if ($this->PermanentFlags) {
|
||||
$result['permanentFlags'] = $this->PermanentFlags;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,13 +26,9 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
use Commands\Metadata;
|
||||
use Commands\Quota;
|
||||
|
||||
const
|
||||
TAG_PREFIX = 'TAG';
|
||||
public string $TAG_PREFIX = 'TAG';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iTagCount = 0;
|
||||
private int $iTagCount = 0;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
|
|
@ -46,39 +42,22 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
|
||||
/**
|
||||
* Used by \MailSo\Mail\MailClient::MessageMimeStream
|
||||
* @var array
|
||||
*/
|
||||
private $aFetchCallbacks;
|
||||
private array $aFetchCallbacks = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aTagTimeouts = array();
|
||||
private array $aTagTimeouts = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bIsLoggined = false;
|
||||
private bool $bIsLoggined = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sLogginedUser = '';
|
||||
private bool $UTF8 = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $__FORCE_SELECT_ON_EXAMINE__ = false;
|
||||
public $__DISABLE_METADATA = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $UTF8 = false;
|
||||
|
||||
public function GetLogginedUser() : string
|
||||
public function Hash() : string
|
||||
{
|
||||
return $this->sLogginedUser;
|
||||
return \md5('ImapClientHash/'.
|
||||
$this->Settings->Login . '@' .
|
||||
$this->Settings->host . ':' .
|
||||
$this->Settings->port
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -87,6 +66,7 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
// public function Connect(Settings $oSettings) : void
|
||||
public function Connect(\MailSo\Net\ConnectSettings $oSettings) : void
|
||||
{
|
||||
$this->aTagTimeouts['*'] = \microtime(true);
|
||||
|
|
@ -95,63 +75,73 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
|
||||
$this->setCapabilities($this->getResponse('*'));
|
||||
|
||||
if (ConnectionSecurityType::STARTTLS === $this->iSecurityType
|
||||
|| (ConnectionSecurityType::AUTO_DETECT === $this->iSecurityType && $this->IsSupported('STARTTLS'))) {
|
||||
if (ConnectionSecurityType::STARTTLS === $this->Settings->type
|
||||
|| (ConnectionSecurityType::AUTO_DETECT === $this->Settings->type && $this->hasCapability('STARTTLS'))) {
|
||||
$this->StartTLS();
|
||||
}
|
||||
}
|
||||
|
||||
private function StartTLS() : void
|
||||
{
|
||||
if ($this->IsSupported('STARTTLS')) {
|
||||
if ($this->hasCapability('STARTTLS')) {
|
||||
$this->SendRequestGetResponse('STARTTLS');
|
||||
$this->EnableCrypto();
|
||||
$this->aCapabilityItems = null;
|
||||
} else {
|
||||
$this->writeLogException(
|
||||
new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'),
|
||||
\LOG_ERR, true);
|
||||
\LOG_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
public function supportsAuthType(string $sasl_type) : bool
|
||||
{
|
||||
return $this->hasCapability("AUTH={$sasl_type}");
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function Login(array $aCredentials) : self
|
||||
public function Login(Settings $oSettings) : self
|
||||
{
|
||||
if (!empty($aCredentials['ProxyAuthUser']) && !empty($aCredentials['ProxyAuthPassword'])) {
|
||||
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($aCredentials['ProxyAuthUser']));
|
||||
$sPassword = $aCredentials['ProxyAuthPassword'];
|
||||
$sProxyAuthUser = $aCredentials['Login'];
|
||||
if ($this->bIsLoggined) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!empty($oSettings->ProxyAuthUser) && !empty($oSettings->ProxyAuthPassword)) {
|
||||
$sLogin = $oSettings->ProxyAuthUser;
|
||||
$sPassword = $oSettings->ProxyAuthPassword;
|
||||
$sProxyAuthUser = $oSettings->Login;
|
||||
} else {
|
||||
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($aCredentials['Login']));
|
||||
$sPassword = $aCredentials['Password'];
|
||||
$sLogin = $oSettings->Login;
|
||||
$sPassword = $oSettings->Password;
|
||||
$sProxyAuthUser = '';
|
||||
}
|
||||
|
||||
if (!\strlen($sLogin) || !\strlen($sPassword))
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \InvalidArgumentException,
|
||||
\LOG_ERR, true);
|
||||
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sLogin));
|
||||
|
||||
if (!\strlen($sLogin) || !\strlen($sPassword)) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
||||
}
|
||||
|
||||
$this->sLogginedUser = $sLogin;
|
||||
|
||||
$type = $this->IsSupported('LOGINDISABLED') ? '' : 'LOGIN'; // RFC3501 6.2.3
|
||||
foreach ($aCredentials['SASLMechanisms'] as $sasl_type) {
|
||||
if ($this->IsSupported("AUTH={$sasl_type}") && \SnappyMail\SASL::isSupported($sasl_type)) {
|
||||
$type = '';
|
||||
foreach ($oSettings->SASLMechanisms as $sasl_type) {
|
||||
if ($this->hasCapability("AUTH={$sasl_type}") && \SnappyMail\SASL::isSupported($sasl_type)) {
|
||||
$type = $sasl_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// RFC3501 6.2.3
|
||||
if (!$type && \in_array('LOGIN', $oSettings->SASLMechanisms) && !$this->hasCapability('LOGINDISABLED')) {
|
||||
$type = 'LOGIN';
|
||||
}
|
||||
if (!$type) {
|
||||
if (!$this->Encrypted() && $this->IsSupported('STARTTLS')) {
|
||||
if (!$this->Encrypted() && $this->hasCapability('STARTTLS')) {
|
||||
$this->StartTLS();
|
||||
return $this->Login($aCredentials);
|
||||
return $this->Login($oSettings);
|
||||
}
|
||||
throw new \MailSo\RuntimeException('No supported SASL mechanism found, remote server wants: '
|
||||
. \implode(', ', \array_filter($this->Capability() ?: [], function($var){
|
||||
|
|
@ -161,16 +151,15 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
}
|
||||
|
||||
$SASL = \SnappyMail\SASL::factory($type);
|
||||
$SASL->base64 = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (0 === \strpos($type, 'SCRAM-'))
|
||||
if (\str_starts_with($type, 'SCRAM-'))
|
||||
{
|
||||
$sAuthzid = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), Enumerations\ResponseType::CONTINUATION);
|
||||
$this->sendRaw($SASL->authenticate($sLogin, $sPassword/*, $sAuthzid*/), true);
|
||||
$sChallenge = $SASL->challenge($this->getResponseValue($this->getResponse(), Enumerations\ResponseType::CONTINUATION));
|
||||
$this->oLogger && $this->oLogger->AddSecret($sChallenge);
|
||||
$this->logMask($sChallenge);
|
||||
$this->sendRaw($sChallenge);
|
||||
$oResponse = $this->getResponse();
|
||||
$SASL->verify($this->getResponseValue($oResponse));
|
||||
|
|
@ -178,17 +167,17 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
else if ('CRAM-MD5' === $type)
|
||||
{
|
||||
$sChallenge = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), Enumerations\ResponseType::CONTINUATION);
|
||||
$this->oLogger->Write('challenge: '.\base64_decode($sChallenge));
|
||||
$this->logWrite('challenge: '.\base64_decode($sChallenge));
|
||||
$sAuth = $SASL->authenticate($sLogin, $sPassword, $sChallenge);
|
||||
$this->oLogger && $this->oLogger->AddSecret($sAuth);
|
||||
$this->logMask($sAuth);
|
||||
$this->sendRaw($sAuth);
|
||||
$oResponse = $this->getResponse();
|
||||
}
|
||||
else if ('PLAIN' === $type || 'OAUTHBEARER' === $type)
|
||||
else if ('PLAIN' === $type || 'OAUTHBEARER' === $type /*|| 'PLAIN-CLIENTTOKEN' === $type*/)
|
||||
{
|
||||
$sAuth = $SASL->authenticate($sLogin, $sPassword);
|
||||
$this->oLogger && $this->oLogger->AddSecret($sAuth);
|
||||
if ($this->IsSupported('SASL-IR')) {
|
||||
$this->logMask($sAuth);
|
||||
if ($this->hasCapability('SASL-IR')) {
|
||||
$oResponse = $this->SendRequestGetResponse('AUTHENTICATE', array($type, $sAuth));
|
||||
} else {
|
||||
$this->SendRequestGetResponse('AUTHENTICATE', array($type));
|
||||
|
|
@ -203,27 +192,26 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
$oR = $oResponse->getLast();
|
||||
if ($oR && Enumerations\ResponseType::CONTINUATION === $oR->ResponseType) {
|
||||
if (!empty($oR->ResponseList[1]) && preg_match('/^[a-zA-Z0-9=+\/]+$/', $oR->ResponseList[1])) {
|
||||
$this->Logger()->Write(\base64_decode($oR->ResponseList[1]),
|
||||
\LOG_WARNING);
|
||||
$this->logWrite(\base64_decode($oR->ResponseList[1]), \LOG_WARNING);
|
||||
}
|
||||
$this->sendRaw('');
|
||||
$oResponse = $this->getResponse();
|
||||
}
|
||||
}
|
||||
else if ($this->IsSupported('LOGINDISABLED'))
|
||||
else if ($this->hasCapability('LOGINDISABLED'))
|
||||
{
|
||||
$sB64 = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), Enumerations\ResponseType::CONTINUATION);
|
||||
$this->sendRaw($SASL->authenticate($sLogin, $sPassword, $sB64), true);
|
||||
$this->getResponse();
|
||||
$sPass = $SASL->challenge(''/*UGFzc3dvcmQ6*/);
|
||||
$this->oLogger && $this->oLogger->AddSecret($sPass);
|
||||
$this->logMask($sPass);
|
||||
$this->sendRaw($sPass);
|
||||
$oResponse = $this->getResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
$sPassword = $this->EscapeString(\mb_convert_encoding($sPassword, 'ISO-8859-1', 'UTF-8'));
|
||||
$this->oLogger && $this->oLogger->AddSecret($sPassword);
|
||||
$this->logMask($sPassword);
|
||||
$oResponse = $this->SendRequestGetResponse('LOGIN',
|
||||
array(
|
||||
$this->EscapeString($sLogin),
|
||||
|
|
@ -233,28 +221,24 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
|
||||
$this->setCapabilities($oResponse);
|
||||
|
||||
if (\strlen($sProxyAuthUser))
|
||||
{
|
||||
if (\strlen($sProxyAuthUser)) {
|
||||
$this->SendRequestGetResponse('PROXYAUTH', array($this->EscapeString($sProxyAuthUser)));
|
||||
}
|
||||
/*
|
||||
// TODO: RFC 9051
|
||||
if ($this->IsSupported('IMAP4rev2')) {
|
||||
if ($this->hasCapability('IMAP4rev2')) {
|
||||
$this->Enable('IMAP4rev1');
|
||||
}
|
||||
*/
|
||||
// RFC 6855 || RFC 5738
|
||||
$this->UTF8 = $this->IsSupported('UTF8=ONLY') || $this->IsSupported('UTF8=ACCEPT');
|
||||
$this->UTF8 = $this->hasCapability('UTF8=ONLY') || $this->hasCapability('UTF8=ACCEPT');
|
||||
if ($this->UTF8) {
|
||||
$this->Enable('UTF8=ACCEPT');
|
||||
}
|
||||
}
|
||||
catch (Exceptions\NegativeResponseException $oException)
|
||||
{
|
||||
$this->writeLogException(
|
||||
new Exceptions\LoginBadCredentialsException(
|
||||
$oException->GetResponses(), '', 0, $oException),
|
||||
\LOG_NOTICE, true);
|
||||
$this->writeLogException(new Exceptions\LoginBadCredentialsException($oException->GetResponses(), '', 0, $oException));
|
||||
}
|
||||
|
||||
$this->bIsLoggined = true;
|
||||
|
|
@ -269,8 +253,7 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function Logout() : void
|
||||
{
|
||||
if ($this->bIsLoggined)
|
||||
{
|
||||
if ($this->bIsLoggined) {
|
||||
$this->bIsLoggined = false;
|
||||
$this->SendRequestGetResponse('LOGOUT');
|
||||
}
|
||||
|
|
@ -302,12 +285,44 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
return $this->aCapabilityItems;
|
||||
}
|
||||
|
||||
public function CapabilityValue(string $sExtentionName) : ?string
|
||||
{
|
||||
$sExtentionName = \trim($sExtentionName) . '=';
|
||||
$aCapabilities = $this->Capability() ?: [];
|
||||
foreach ($aCapabilities as $string) {
|
||||
if (\str_starts_with($string, $sExtentionName)) {
|
||||
return \substr($string, \strlen($sExtentionName));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function setCapabilities(ResponseCollection $oResponseCollection) : void
|
||||
{
|
||||
$aList = $oResponseCollection->getCapabilityResult();
|
||||
if ($aList && $this->__DISABLE_METADATA) {
|
||||
// Issue #365: Many folders on Cyrus IMAP breaks login
|
||||
$aList = \array_diff($aList, ['METADATA']);
|
||||
if ($aList) {
|
||||
if ($this->Settings->disable_metadata) {
|
||||
// Issue #365: Many folders on Cyrus IMAP breaks login
|
||||
$aList = \array_diff($aList, ['METADATA']);
|
||||
}
|
||||
if ($this->Settings->disable_move) {
|
||||
$aList = \array_diff($aList, ['MOVE']);
|
||||
}
|
||||
if ($this->Settings->disable_sort) {
|
||||
$aList = \array_diff($aList, ['SORT','ESORT']);
|
||||
}
|
||||
if ($this->Settings->disable_thread) {
|
||||
$aList = \array_filter($aList, function ($item) { \str_starts_with($item, 'THREAD='); });
|
||||
}
|
||||
if ($this->Settings->disable_list_status) {
|
||||
$aList = \array_diff($aList, ['LIST-STATUS']);
|
||||
}
|
||||
if ($this->Settings->disable_binary) {
|
||||
$aList = \array_diff($aList, ['BINARY']);
|
||||
}
|
||||
if (8 > PHP_INT_SIZE) {
|
||||
$aList = \array_diff($aList, ['CONDSTORE']);
|
||||
}
|
||||
}
|
||||
$this->aCapabilityItems = $aList;
|
||||
}
|
||||
|
|
@ -326,11 +341,15 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Imap\Exceptions\*
|
||||
*/
|
||||
public function IsSupported(string $sExtentionName) : bool
|
||||
public function hasCapability(string $sExtentionName) : bool
|
||||
{
|
||||
$sExtentionName = \trim($sExtentionName);
|
||||
return $sExtentionName && \in_array(\strtoupper($sExtentionName), $this->Capability() ?: []);
|
||||
}
|
||||
public function IsSupported(string $sExtentionName) : bool
|
||||
{
|
||||
return $this->hasCapability($sExtentionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* RFC 5161
|
||||
|
|
@ -352,7 +371,7 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function GetNamespace() : ?NamespaceResult
|
||||
{
|
||||
if (!$this->IsSupported('NAMESPACE')) {
|
||||
if (!$this->hasCapability('NAMESPACE')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -362,18 +381,21 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
if (Enumerations\ResponseType::UNTAGGED === $oResponse->ResponseType
|
||||
&& 'NAMESPACE' === $oResponse->StatusOrIndex)
|
||||
{
|
||||
$oReturn = new NamespaceResult;
|
||||
$oReturn->InitByImapResponse($oResponse);
|
||||
return $oReturn;
|
||||
return new NamespaceResult($oResponse);
|
||||
}
|
||||
}
|
||||
throw new Exceptions\ResponseException;
|
||||
} catch (\Throwable $e) {
|
||||
$this->writeLogException($e, \LOG_ERR);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetPersonalNamespace() : string
|
||||
{
|
||||
$oNamespace = $this->GetNamespace();
|
||||
return $oNamespace ? $oNamespace->GetPersonalNamespace() : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* RFC 7889
|
||||
* APPENDLIMIT=<number> indicates that the IMAP server has the same upload limit for all mailboxes.
|
||||
|
|
@ -383,14 +405,8 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function AppendLimit() : ?int
|
||||
{
|
||||
if ($this->Capability()) {
|
||||
foreach ($this->aCapabilityItems as $string) {
|
||||
if ('APPENDLIMIT=' === \substr($string, 0, 12)) {
|
||||
return (int) \substr($string, 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
$string = $this->CapabilityValue('APPENDLIMIT');
|
||||
return \is_null($string) ? null : (int) $string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -414,7 +430,7 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
{
|
||||
$sCommand = \trim($sCommand);
|
||||
if (!\strlen($sCommand)) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR, true);
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
||||
}
|
||||
|
||||
$this->IsConnected(true);
|
||||
|
|
@ -457,13 +473,9 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
if ($sResult) {
|
||||
return $sResult;
|
||||
}
|
||||
$this->writeLogException(
|
||||
new Exceptions\LoginException,
|
||||
\LOG_NOTICE, true);
|
||||
$this->writeLogException(new Exceptions\LoginException);
|
||||
}
|
||||
$this->writeLogException(
|
||||
new Exceptions\LoginException,
|
||||
\LOG_NOTICE, true);
|
||||
$this->writeLogException(new Exceptions\LoginException);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -494,7 +506,6 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->writeLogException($e, \LOG_WARNING);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -532,7 +543,6 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
|
||||
} catch (\Throwable $e) {
|
||||
$this->writeLogException($e, \LOG_WARNING);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $oResult;
|
||||
|
|
@ -577,21 +587,16 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
|
||||
} catch (\Throwable $e) {
|
||||
$this->writeLogException($e, \LOG_WARNING);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
protected function prepareParamLine(array $aParams = array()) : string
|
||||
{
|
||||
$sReturn = '';
|
||||
foreach ($aParams as $mParamItem)
|
||||
{
|
||||
if (\is_array($mParamItem) && \count($mParamItem))
|
||||
{
|
||||
foreach ($aParams as $mParamItem) {
|
||||
if (\is_array($mParamItem) && \count($mParamItem)) {
|
||||
$sReturn .= ' ('.\trim($this->prepareParamLine($mParamItem)).')';
|
||||
}
|
||||
else if (\is_string($mParamItem))
|
||||
{
|
||||
} else if (\is_string($mParamItem)) {
|
||||
$sReturn .= ' '.$mParamItem;
|
||||
}
|
||||
}
|
||||
|
|
@ -606,7 +611,7 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
|
||||
protected function getCurrentTag() : string
|
||||
{
|
||||
return self::TAG_PREFIX.$this->iTagCount;
|
||||
return $this->TAG_PREFIX.$this->iTagCount;
|
||||
}
|
||||
|
||||
public function EscapeString(?string $sStringForEscape) : string
|
||||
|
|
@ -616,6 +621,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);
|
||||
}
|
||||
|
|
@ -635,7 +641,7 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function ServerID() : string
|
||||
{
|
||||
if ($this->IsSupported('ID')) {
|
||||
if ($this->hasCapability('ID')) {
|
||||
foreach ($this->SendRequestGetResponse('ID', [null]) as $oResponse) {
|
||||
if ('ID' === $oResponse->ResponseList[1] && \is_array($oResponse->ResponseList[2])) {
|
||||
$c = \count($oResponse->ResponseList[2]);
|
||||
|
|
@ -657,7 +663,7 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
public function Compress() : bool
|
||||
{
|
||||
try {
|
||||
if ($this->IsSupported('COMPRESS=DEFLATE')) {
|
||||
if ($this->hasCapability('COMPRESS=DEFLATE')) {
|
||||
$this->SendRequestGetResponse('COMPRESS', ['DEFLATE']);
|
||||
\stream_filter_append($this->ConnectionResource(), 'zlib.inflate');
|
||||
\stream_filter_append($this->ConnectionResource(), 'zlib.deflate', STREAM_FILTER_WRITE, array(
|
||||
|
|
|
|||
|
|
@ -17,75 +17,38 @@ namespace MailSo\Imap;
|
|||
*/
|
||||
class NamespaceResult
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sPersonal = '';
|
||||
private string $sPersonal = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sPersonalDelimiter = '';
|
||||
private string $sPersonalDelimiter = '';
|
||||
/*
|
||||
private string $sOtherUser = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sOtherUser = '';
|
||||
private string $sOtherUserDelimiter = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sOtherUserDelimiter = '';
|
||||
private string $sShared = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sShared = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sSharedDelimiter = '';
|
||||
|
||||
public function InitByImapResponse(\MailSo\Imap\Response $oImapResponse) : self
|
||||
private string $sSharedDelimiter = '';
|
||||
*/
|
||||
function __construct(Response $oImapResponse)
|
||||
{
|
||||
if ($oImapResponse)
|
||||
{
|
||||
if (isset($oImapResponse->ResponseList[2][0]) &&
|
||||
\is_array($oImapResponse->ResponseList[2][0]) &&
|
||||
2 <= \count($oImapResponse->ResponseList[2][0]))
|
||||
{
|
||||
$this->sPersonal = $oImapResponse->ResponseList[2][0][0];
|
||||
$this->sPersonalDelimiter = $oImapResponse->ResponseList[2][0][1];
|
||||
|
||||
$this->sPersonal = 'INBOX'.$this->sPersonalDelimiter === \substr(\strtoupper($this->sPersonal), 0, 6) ?
|
||||
'INBOX'.$this->sPersonalDelimiter.\substr($this->sPersonal, 6) : $this->sPersonal;
|
||||
}
|
||||
|
||||
if (isset($oImapResponse->ResponseList[3][0]) &&
|
||||
\is_array($oImapResponse->ResponseList[3][0]) &&
|
||||
2 <= \count($oImapResponse->ResponseList[3][0]))
|
||||
{
|
||||
$this->sOtherUser = $oImapResponse->ResponseList[3][0][0];
|
||||
$this->sOtherUserDelimiter = $oImapResponse->ResponseList[3][0][1];
|
||||
|
||||
$this->sOtherUser = 'INBOX'.$this->sOtherUserDelimiter === \substr(\strtoupper($this->sOtherUser), 0, 6) ?
|
||||
'INBOX'.$this->sOtherUserDelimiter.\substr($this->sOtherUser, 6) : $this->sOtherUser;
|
||||
}
|
||||
|
||||
if (isset($oImapResponse->ResponseList[4][0]) &&
|
||||
\is_array($oImapResponse->ResponseList[4][0]) &&
|
||||
2 <= \count($oImapResponse->ResponseList[4][0]))
|
||||
{
|
||||
$this->sShared = $oImapResponse->ResponseList[4][0][0];
|
||||
$this->sSharedDelimiter = $oImapResponse->ResponseList[4][0][1];
|
||||
|
||||
$this->sShared = 'INBOX'.$this->sSharedDelimiter === \substr(\strtoupper($this->sShared), 0, 6) ?
|
||||
'INBOX'.$this->sSharedDelimiter.\substr($this->sShared, 6) : $this->sShared;
|
||||
}
|
||||
$space = static::getNamespace($oImapResponse, 2);
|
||||
if ($space) {
|
||||
$this->sPersonal = $space[0];
|
||||
$this->sPersonalDelimiter = $space[1];
|
||||
}
|
||||
/*
|
||||
$space = static::getNamespace($oImapResponse, 3);
|
||||
if ($space) {
|
||||
$this->sOtherUser = $space[0];
|
||||
$this->sOtherUserDelimiter = $space[1];
|
||||
}
|
||||
|
||||
return $this;
|
||||
$space = static::getNamespace($oImapResponse, 4);
|
||||
if ($space) {
|
||||
$this->sShared = $space[0];
|
||||
$this->sSharedDelimiter = $space[1];
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public function GetPersonalNamespace() : string
|
||||
|
|
@ -93,4 +56,20 @@ class NamespaceResult
|
|||
return $this->sPersonal;
|
||||
}
|
||||
|
||||
private static function getNamespace(Response $oImapResponse, int $section) : ?array
|
||||
{
|
||||
if (isset($oImapResponse->ResponseList[$section][0])
|
||||
&& \is_array($oImapResponse->ResponseList[$section][0])
|
||||
&& 2 <= \count($oImapResponse->ResponseList[$section][0]))
|
||||
{
|
||||
$sName = $oImapResponse->ResponseList[$section][0][0];
|
||||
$sDelimiter = $oImapResponse->ResponseList[$section][0][1];
|
||||
$sName = 'INBOX'.$sDelimiter === \substr(\strtoupper($sName), 0, 6)
|
||||
? 'INBOX'.$sDelimiter.\substr($sName, 6)
|
||||
: $sName;
|
||||
return [$sName, $sDelimiter];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ namespace MailSo\Imap\Requests;
|
|||
*/
|
||||
class ESEARCH extends Request
|
||||
{
|
||||
public
|
||||
$sCriterias = 'ALL',
|
||||
$aReturn = [
|
||||
public string $sCriterias = 'ALL';
|
||||
|
||||
public array $aReturn = [
|
||||
/**
|
||||
ALL
|
||||
Return all message numbers/UIDs which match the search criteria,
|
||||
|
|
@ -41,21 +41,23 @@ class ESEARCH extends Request
|
|||
Return all message numbers/UIDs which match the search criteria,
|
||||
in the requested sort order, using a sequence-set.
|
||||
*/
|
||||
],
|
||||
$bUid = true,
|
||||
$sLimit = '',
|
||||
$sCharset = '',
|
||||
// https://datatracker.ietf.org/doc/html/rfc7377
|
||||
$aMailboxes = [],
|
||||
$aSubtrees = [],
|
||||
$aSubtreesOne = [];
|
||||
];
|
||||
|
||||
public bool $bUid = true;
|
||||
|
||||
public string $sLimit = '';
|
||||
|
||||
public string $sCharset = '';
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc7377
|
||||
public array $aMailboxes = [];
|
||||
public array $aSubtrees = [];
|
||||
public array $aSubtreesOne = [];
|
||||
|
||||
function __construct(\MailSo\Imap\ImapClient $oImapClient)
|
||||
{
|
||||
if (!$oImapClient->IsSupported('ESEARCH')) {
|
||||
$oImapClient->writeLogException(
|
||||
new \MailSo\RuntimeException('ESEARCH is not supported'),
|
||||
\LOG_ERR, true);
|
||||
if (!$oImapClient->hasCapability('ESEARCH')) {
|
||||
$oImapClient->writeLogException(new \MailSo\RuntimeException('ESEARCH is not supported'), \LOG_ERR);
|
||||
}
|
||||
parent::__construct($oImapClient);
|
||||
}
|
||||
|
|
@ -66,10 +68,8 @@ class ESEARCH extends Request
|
|||
$aRequest = array();
|
||||
|
||||
/* // RFC 6203
|
||||
if (false !== \stripos($this->sCriterias, 'FUZZY') && !$this->oImapClient->IsSupported('SEARCH=FUZZY')) {
|
||||
$this->oImapClient->writeLogException(
|
||||
new \MailSo\RuntimeException('SEARCH=FUZZY is not supported'),
|
||||
\LOG_ERR, true);
|
||||
if (false !== \stripos($this->sCriterias, 'FUZZY') && !$this->oImapClient->hasCapability('SEARCH=FUZZY')) {
|
||||
$this->oImapClient->writeLogException(new \MailSo\RuntimeException('SEARCH=FUZZY is not supported'), \LOG_ERR);
|
||||
}
|
||||
*/
|
||||
|
||||
|
|
@ -87,10 +87,8 @@ class ESEARCH extends Request
|
|||
$aFolders[] = $this->aSubtreesOne;
|
||||
}
|
||||
if ($aFolders) {
|
||||
if (!$this->oImapClient->IsSupported('MULTISEARCH')) {
|
||||
$this->oImapClient->writeLogException(
|
||||
new \MailSo\RuntimeException('MULTISEARCH is not supported'),
|
||||
\LOG_ERR, true);
|
||||
if (!$this->oImapClient->hasCapability('MULTISEARCH')) {
|
||||
$this->oImapClient->writeLogException(new \MailSo\RuntimeException('MULTISEARCH is not supported'), \LOG_ERR);
|
||||
}
|
||||
$sCmd = 'ESEARCH';
|
||||
$aReques[] = 'IN';
|
||||
|
|
@ -105,12 +103,10 @@ class ESEARCH extends Request
|
|||
$aRequest[] = 'RETURN';
|
||||
if ($this->aReturn) {
|
||||
// RFC 5267 checks
|
||||
if (!$this->oImapClient->IsSupported('CONTEXT=SEARCH')) {
|
||||
if (!$this->oImapClient->hasCapability('CONTEXT=SEARCH')) {
|
||||
foreach ($this->aReturn as $sReturn) {
|
||||
if (\preg_match('/PARTIAL|UPDATE|CONTEXT/i', $sReturn)) {
|
||||
$this->oImapClient->writeLogException(
|
||||
new \MailSo\RuntimeException('CONTEXT=SEARCH is not supported'),
|
||||
\LOG_ERR, true);
|
||||
$this->oImapClient->writeLogException(new \MailSo\RuntimeException('CONTEXT=SEARCH is not supported'), \LOG_ERR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ namespace MailSo\Imap\Requests;
|
|||
|
||||
abstract class Request
|
||||
{
|
||||
protected
|
||||
$oImapClient;
|
||||
protected \MailSo\Imap\ImapClient $oImapClient;
|
||||
|
||||
function __construct(\MailSo\Imap\ImapClient $oImapClient)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,14 +21,18 @@ namespace MailSo\Imap\Requests;
|
|||
*/
|
||||
class SORT extends Request
|
||||
{
|
||||
public
|
||||
$sCriterias = 'ALL',
|
||||
$sCharset = '',
|
||||
$bUid = true,
|
||||
$aSortTypes = [],
|
||||
$sLimit = '',
|
||||
// RFC 5267
|
||||
$aReturn = [
|
||||
public string $sCriterias = 'ALL';
|
||||
|
||||
public string $sCharset = '';
|
||||
|
||||
public bool $bUid = true;
|
||||
|
||||
public array $aSortTypes = [];
|
||||
|
||||
public string $sLimit = '';
|
||||
|
||||
// RFC 5267
|
||||
public array $aReturn = [
|
||||
/**
|
||||
ALL
|
||||
Return all message numbers/UIDs which match the search criteria,
|
||||
|
|
@ -53,10 +57,8 @@ class SORT extends Request
|
|||
|
||||
function __construct(\MailSo\Imap\ImapClient $oImapClient)
|
||||
{
|
||||
if (!$oImapClient->IsSupported('SORT')) {
|
||||
$oImapClient->writeLogException(
|
||||
new \MailSo\RuntimeException('SORT is not supported'),
|
||||
\LOG_ERR, true);
|
||||
if (!$oImapClient->hasCapability('SORT')) {
|
||||
$oImapClient->writeLogException(new \MailSo\RuntimeException('SORT is not supported'), \LOG_ERR);
|
||||
}
|
||||
parent::__construct($oImapClient);
|
||||
}
|
||||
|
|
@ -64,26 +66,20 @@ class SORT extends Request
|
|||
public function SendRequest() : string
|
||||
{
|
||||
if (!$this->aSortTypes) {
|
||||
$this->oImapClient->writeLogException(
|
||||
new \MailSo\RuntimeException('SortTypes are missing'),
|
||||
\LOG_ERR, true);
|
||||
$this->oImapClient->writeLogException(new \MailSo\RuntimeException('SortTypes are missing'), \LOG_ERR);
|
||||
}
|
||||
|
||||
$aRequest = array();
|
||||
|
||||
if ($this->aReturn) {
|
||||
// RFC 5267 checks
|
||||
if (!$this->oImapClient->IsSupported('ESORT')) {
|
||||
$this->oImapClient->writeLogException(
|
||||
new \MailSo\RuntimeException('ESORT is not supported'),
|
||||
\LOG_ERR, true);
|
||||
if (!$this->oImapClient->hasCapability('ESORT')) {
|
||||
$this->oImapClient->writeLogException(new \MailSo\RuntimeException('ESORT is not supported'), \LOG_ERR);
|
||||
}
|
||||
if (!$this->oImapClient->IsSupported('CONTEXT=SORT')) {
|
||||
if (!$this->oImapClient->hasCapability('CONTEXT=SORT')) {
|
||||
foreach ($this->aReturn as $sReturn) {
|
||||
if (\preg_match('/PARTIAL|UPDATE|CONTEXT/i', $sReturn)) {
|
||||
$this->oImapClient->writeLogException(
|
||||
new \MailSo\RuntimeException('CONTEXT=SORT is not supported'),
|
||||
\LOG_ERR, true);
|
||||
$this->oImapClient->writeLogException(new \MailSo\RuntimeException('CONTEXT=SORT is not supported'), \LOG_ERR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,35 +20,38 @@ namespace MailSo\Imap\Requests;
|
|||
*/
|
||||
class THREAD extends Request
|
||||
{
|
||||
public
|
||||
$sAlgorithm = '', // ORDEREDSUBJECT or REFERENCES or REFS
|
||||
$sCriterias = 'ALL',
|
||||
$bUid = true;
|
||||
// ORDEREDSUBJECT or REFERENCES or REFS
|
||||
private string $sAlgorithm = '';
|
||||
|
||||
public string $sCriterias = 'ALL';
|
||||
|
||||
public bool $bUid = true;
|
||||
|
||||
function __construct(\MailSo\Imap\ImapClient $oImapClient)
|
||||
{
|
||||
if ($oImapClient->IsSupported('THREAD=REFS')) {
|
||||
if ($oImapClient->hasCapability('THREAD=REFS')) {
|
||||
$this->sAlgorithm = 'REFS';
|
||||
} else if ($oImapClient->IsSupported('THREAD=REFERENCES')) {
|
||||
} else if ($oImapClient->hasCapability('THREAD=REFERENCES')) {
|
||||
$this->sAlgorithm = 'REFERENCES';
|
||||
} else if ($oImapClient->IsSupported('THREAD=ORDEREDSUBJECT')) {
|
||||
} else if ($oImapClient->hasCapability('THREAD=ORDEREDSUBJECT')) {
|
||||
$this->sAlgorithm = 'ORDEREDSUBJECT';
|
||||
} else {
|
||||
$oImapClient->writeLogException(
|
||||
new \MailSo\RuntimeException('THREAD is not supported'),
|
||||
\LOG_ERR, true);
|
||||
$oImapClient->writeLogException(new \MailSo\RuntimeException('THREAD is not supported'), \LOG_ERR);
|
||||
}
|
||||
parent::__construct($oImapClient);
|
||||
}
|
||||
|
||||
public function setAlgorithm(string $sAlgorithm) : void
|
||||
{
|
||||
$sAlgorithm = \strtoupper($sAlgorithm);
|
||||
if (!$this->oImapClient->hasCapability("THREAD={$sAlgorithm}")) {
|
||||
$this->oImapClient->writeLogException(new \MailSo\RuntimeException("THREAD={$sAlgorithm} is not supported"), \LOG_ERR);
|
||||
}
|
||||
$this->sAlgorithm = $sAlgorithm;
|
||||
}
|
||||
|
||||
public function SendRequestIterateResponse() : iterable
|
||||
{
|
||||
if (!$this->oImapClient->IsSupported(\strtoupper("THREAD={$this->sAlgorithm}"))) {
|
||||
$this->oImapClient->writeLogException(
|
||||
new \MailSo\RuntimeException("THREAD={$this->sAlgorithm} is not supported"),
|
||||
\LOG_ERR, true);
|
||||
}
|
||||
|
||||
$this->oImapClient->SendRequest(
|
||||
($this->bUid ? 'UID THREAD' : 'THREAD'),
|
||||
array(
|
||||
|
|
|
|||
|
|
@ -19,46 +19,24 @@ use MailSo\Imap\Enumerations\ResponseType;
|
|||
*/
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $ResponseList = array();
|
||||
public array $ResponseList = array();
|
||||
|
||||
/**
|
||||
* @var array | null
|
||||
*/
|
||||
public $OptionalResponse = null;
|
||||
public ?array $OptionalResponse = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $StatusOrIndex;
|
||||
public ?string $StatusOrIndex = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $HumanReadable = '';
|
||||
public string $HumanReadable = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $IsStatusResponse = false;
|
||||
public bool $IsStatusResponse = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $ResponseType = 0;
|
||||
public int $ResponseType = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $Tag;
|
||||
public ?string $Tag = null;
|
||||
|
||||
private function recToLine(array $aList) : string
|
||||
{
|
||||
$aResult = array();
|
||||
foreach ($aList as $mItem)
|
||||
{
|
||||
foreach ($aList as $mItem) {
|
||||
$aResult[] = \is_array($mItem) ? '('.$this->recToLine($mItem).')' : (string) $mItem;
|
||||
}
|
||||
return \implode(' ', $aResult);
|
||||
|
|
|
|||
|
|
@ -67,9 +67,7 @@ class ResponseCollection extends \MailSo\Base\Collection
|
|||
}
|
||||
|
||||
if (\is_array($aList) && \count($aList)) {
|
||||
$aList = \array_map('strtoupper', $aList);
|
||||
\sort($aList);
|
||||
return $aList;
|
||||
return \array_map('strtoupper', $aList);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace MailSo\Imap\Responses;
|
|||
*/
|
||||
class ACL implements \JsonSerializable
|
||||
{
|
||||
private $rights;
|
||||
private array $rights;
|
||||
|
||||
function __construct(array $rights)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ namespace MailSo\Imap;
|
|||
abstract class SearchCriterias
|
||||
{
|
||||
const
|
||||
RegEx = 'in|e?mail|from|to|subject|has|is|date|since|before|text|body|size|larger|bigger|smaller|maxsize|minsize|keyword|older_than|newer_than';
|
||||
|
||||
RegEx = 'in|e?mail|from|to|subject|has|is|date|since|before|text|body|size|larger|bigger|smaller|maxsize|minsize|keyword|older_than|newer_than|on|senton|sentsince|sentbefore';
|
||||
/**
|
||||
https://datatracker.ietf.org/doc/html/rfc3501#section-6.4.4
|
||||
|
||||
|
|
@ -66,22 +65,22 @@ abstract class SearchCriterias
|
|||
☐ NOT <search-key>
|
||||
Messages that do not match the specified search key.
|
||||
|
||||
☐ ON <date>
|
||||
✔ ON <date>
|
||||
Messages whose internal date (disregarding time and timezone)
|
||||
is within the specified date.
|
||||
|
||||
✔ OR <search-key1> <search-key2>
|
||||
Messages that match either search key.
|
||||
|
||||
☐ SENTBEFORE <date>
|
||||
✔ SENTBEFORE <date>
|
||||
Messages whose [RFC-2822] Date: header (disregarding time and
|
||||
timezone) is earlier than the specified date.
|
||||
|
||||
☐ SENTON <date>
|
||||
✔ SENTON <date>
|
||||
Messages whose [RFC-2822] Date: header (disregarding time and
|
||||
timezone) is within the specified date.
|
||||
|
||||
☐ SENTSINCE <date>
|
||||
✔ SENTSINCE <date>
|
||||
Messages whose [RFC-2822] Date: header (disregarding time and
|
||||
timezone) is within or later than the specified date.
|
||||
|
||||
|
|
@ -132,11 +131,6 @@ abstract class SearchCriterias
|
|||
$iTimeFilter = 0;
|
||||
$aCriteriasResult = array();
|
||||
|
||||
if (0 < \MailSo\Config::$MessageListDateFilter) {
|
||||
$iD = \time() - 3600 * 24 * 30 * \MailSo\Config::$MessageListDateFilter;
|
||||
$iTimeFilter = \gmmktime(1, 1, 1, \gmdate('n', $iD), 1, \gmdate('Y', $iD));
|
||||
}
|
||||
|
||||
$sSearch = \trim($sSearch);
|
||||
if (\strlen($sSearch)) {
|
||||
$aLines = \preg_match('/^('.static::RegEx.'):/i', $sSearch)
|
||||
|
|
@ -146,7 +140,7 @@ abstract class SearchCriterias
|
|||
if (!$aLines) {
|
||||
$sValue = static::escapeSearchString($oImapClient, $sSearch);
|
||||
|
||||
if (\MailSo\Config::$MessageListFastSimpleSearch) {
|
||||
if ($oImapClient->Settings->fast_simple_search) {
|
||||
$aCriteriasResult[] = 'OR OR OR';
|
||||
$aCriteriasResult[] = 'FROM';
|
||||
$aCriteriasResult[] = $sValue;
|
||||
|
|
@ -161,7 +155,7 @@ abstract class SearchCriterias
|
|||
$aCriteriasResult[] = $sValue;
|
||||
}
|
||||
} else {
|
||||
if (isset($aLines['IN']) && $oImapClient->IsSupported('MULTISEARCH') && \in_array($aLines['IN'], ['subtree','subtree-one','mailboxes'])) {
|
||||
if (isset($aLines['IN']) && $oImapClient->hasCapability('MULTISEARCH') && \in_array($aLines['IN'], ['subtree','subtree-one','mailboxes'])) {
|
||||
$aCriteriasResult[] = "IN ({$aLines['IN']} \"{$sFolderName}\")";
|
||||
}
|
||||
|
||||
|
|
@ -242,10 +236,14 @@ abstract class SearchCriterias
|
|||
$iTimeFilter = \max($iTimeFilter, $sValue);
|
||||
}
|
||||
break;
|
||||
case 'ON':
|
||||
case 'SENTON':
|
||||
case 'SENTSINCE':
|
||||
case 'SENTBEFORE':
|
||||
case 'BEFORE':
|
||||
$sValue = static::parseSearchDate($sRawValue);
|
||||
if ($sValue) {
|
||||
$aCriteriasResult[] = 'BEFORE';
|
||||
$aCriteriasResult[] = $sName;
|
||||
$aCriteriasResult[] = \gmdate('j-M-Y', $sValue);
|
||||
}
|
||||
break;
|
||||
|
|
@ -307,8 +305,8 @@ abstract class SearchCriterias
|
|||
$aCriteriasResult[] = 'UNDELETED';
|
||||
}
|
||||
|
||||
if (\MailSo\Config::$MessageListPermanentFilter) {
|
||||
$aCriteriasResult[] = \MailSo\Config::$MessageListPermanentFilter;
|
||||
if ($oImapClient->Settings->search_filter) {
|
||||
$aCriteriasResult[] = $oImapClient->Settings->search_filter;
|
||||
}
|
||||
|
||||
$sCriteriasResult = \trim(\implode(' ', $aCriteriasResult));
|
||||
|
|
@ -318,8 +316,12 @@ 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);
|
||||
// https://github.com/the-djmaze/snappymail/issues/836
|
||||
// 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
|
||||
|
|
@ -379,6 +381,10 @@ abstract class SearchCriterias
|
|||
case 'SMALLER':
|
||||
case 'LARGER':
|
||||
case 'SINCE':
|
||||
case 'ON':
|
||||
case 'SENTON':
|
||||
case 'SENTSINCE':
|
||||
case 'SENTBEFORE':
|
||||
case 'BEFORE':
|
||||
if (\strlen($mValue)) {
|
||||
$aResult[$sName] = $mValue;
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ class SequenceSet /*extends \SplFixedArray*/ implements \Countable
|
|||
/**
|
||||
* By default the numbers are unique identifiers as this is more reliable.
|
||||
*/
|
||||
public $UID = true;
|
||||
public bool $UID = true;
|
||||
|
||||
private $data = [];
|
||||
private array $data = [];
|
||||
|
||||
public function __construct($mItems, bool $uid = true)
|
||||
{
|
||||
|
|
|
|||
106
snappymail/v/0.0.0/app/libraries/MailSo/Imap/Settings.php
Normal file
106
snappymail/v/0.0.0/app/libraries/MailSo/Imap/Settings.php
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2022 DJMaze
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo\Imap;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Net
|
||||
*/
|
||||
class Settings extends \MailSo\Net\ConnectSettings
|
||||
{
|
||||
public int
|
||||
$port = 143,
|
||||
$timeout = 300;
|
||||
|
||||
public bool
|
||||
$disable_list_status = false,
|
||||
$disable_metadata = false,
|
||||
$disable_move = false,
|
||||
$disable_sort = false,
|
||||
$disable_thread = false,
|
||||
$disable_binary = false,
|
||||
$expunge_all_on_delete = false,
|
||||
$fast_simple_search = true,
|
||||
$fetch_new_messages = true,
|
||||
$force_select = false,
|
||||
$message_all_headers = false;
|
||||
|
||||
public int
|
||||
$body_text_limit = 0,
|
||||
// $folder_list_limit = 200,
|
||||
$message_list_limit = 0,
|
||||
$thread_limit = 50;
|
||||
|
||||
public string
|
||||
$search_filter = '';
|
||||
|
||||
public static function fromArray(array $aSettings) : self
|
||||
{
|
||||
$object = parent::fromArray($aSettings);
|
||||
$options = [
|
||||
'disable_list_status',
|
||||
'disable_metadata',
|
||||
'disable_move',
|
||||
'disable_sort',
|
||||
'disable_thread',
|
||||
'disable_binary',
|
||||
'expunge_all_on_delete',
|
||||
'fast_simple_search',
|
||||
'fetch_new_messages',
|
||||
'force_select',
|
||||
'message_all_headers'
|
||||
];
|
||||
foreach ($options as $option) {
|
||||
if (isset($aSettings[$option])) {
|
||||
$object->$option = !empty($aSettings[$option]);
|
||||
}
|
||||
}
|
||||
$options = [
|
||||
// 'body_text_limit',
|
||||
// 'folder_list_limit',
|
||||
'message_list_limit',
|
||||
// 'thread_limit',
|
||||
];
|
||||
foreach ($options as $option) {
|
||||
if (isset($aSettings[$option])) {
|
||||
$object->$option = \intval($aSettings[$option]);
|
||||
}
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return \array_merge(
|
||||
parent::jsonSerialize(),
|
||||
[
|
||||
// '@Object' => 'Object/ImapSettings',
|
||||
'disable_list_status' => $this->disable_list_status,
|
||||
'disable_metadata' => $this->disable_metadata,
|
||||
'disable_move' => $this->disable_move,
|
||||
'disable_sort' => $this->disable_sort,
|
||||
'disable_thread' => $this->disable_thread,
|
||||
'disable_binary' => $this->disable_binary,
|
||||
'use_expunge_all_on_delete' => $this->expunge_all_on_delete,
|
||||
// 'body_text_limit' => $this->body_text_limit,
|
||||
'fast_simple_search' => $this->fast_simple_search,
|
||||
// 'folder_list_limit' => $this->folder_list_limit,
|
||||
'force_select' => $this->force_select,
|
||||
'message_all_headers' => $this->message_all_headers,
|
||||
'message_list_limit' => $this->message_list_limit,
|
||||
'search_filter' => $this->search_filter,
|
||||
// 'thread_limit' => $this->thread_limit
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,15 +21,9 @@ use MailSo\Imap\Exceptions\ResponseNotFoundException;
|
|||
*/
|
||||
trait ResponseParser
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iResponseBufParsedPos;
|
||||
private int $iResponseBufParsedPos;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bNeedNext = true;
|
||||
private bool $bNeedNext = true;
|
||||
|
||||
protected function partialParseResponse() : Response
|
||||
{
|
||||
|
|
@ -66,6 +60,8 @@ trait ResponseParser
|
|||
return !$oImapResponse->IsStatusResponse || 2 < \count($oImapResponse->ResponseList);
|
||||
}
|
||||
|
||||
private $sResponseBuffer = '';
|
||||
|
||||
/**
|
||||
* @return array|string
|
||||
* @throws \MailSo\RuntimeException
|
||||
|
|
@ -87,35 +83,25 @@ trait ResponseParser
|
|||
|
||||
$sAtomBuilder = $bTreatAsAtom ? '' : null;
|
||||
$aList = array();
|
||||
if ($bRoot)
|
||||
{
|
||||
if ($bRoot) {
|
||||
$aList =& $oImapResponse->ResponseList;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if ($this->bNeedNext)
|
||||
{
|
||||
/**
|
||||
* $this->sResponseBuffer is a single fgets() that ends with \r\n
|
||||
*/
|
||||
while (true) {
|
||||
if ($this->bNeedNext) {
|
||||
$iPos = 0;
|
||||
$this->getNextBuffer();
|
||||
$this->sResponseBuffer = $this->getNextBuffer();
|
||||
$this->iResponseBufParsedPos = $iPos;
|
||||
$this->bNeedNext = false;
|
||||
}
|
||||
|
||||
$sChar = null;
|
||||
if ($bIsGotoDefault)
|
||||
{
|
||||
if ($bIsGotoDefault) {
|
||||
$bIsGotoDefault = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$iBufferEndIndex = \strlen($this->sResponseBuffer) - 3;
|
||||
|
||||
if ($iPos > $iBufferEndIndex)
|
||||
{
|
||||
if ($iPos > $iBufferEndIndex) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -185,9 +171,7 @@ trait ResponseParser
|
|||
if (null !== $sLiteral) {
|
||||
if (!$bTreatAsAtom) {
|
||||
$aList[] = $sLiteral;
|
||||
if (\MailSo\Config::$LogSimpleLiterals) {
|
||||
$this->writeLog('{'.$iLiteralLen.'} '.$sLiteral, \LOG_INFO);
|
||||
}
|
||||
// $this->writeLog('{'.$iLiteralLen.'} '.$sLiteral, \LOG_INFO);
|
||||
} else {
|
||||
\SnappyMail\Log::notice('IMAP', 'Literal treated as atom and skipped');
|
||||
}
|
||||
|
|
@ -255,16 +239,14 @@ trait ResponseParser
|
|||
default:
|
||||
$iCharBlockStartPos = $iPos;
|
||||
|
||||
if ($bRoot && $oImapResponse->IsStatusResponse)
|
||||
{
|
||||
if ($bRoot && $oImapResponse->IsStatusResponse) {
|
||||
$iPos = $iBufferEndIndex;
|
||||
if ($iPos > $iCharBlockStartPos) {
|
||||
$iCharBlockStartPos += \strspn($this->sResponseBuffer, ' ', $iCharBlockStartPos, $iPos - $iCharBlockStartPos);
|
||||
}
|
||||
}
|
||||
|
||||
while ($iPos <= $iBufferEndIndex)
|
||||
{
|
||||
while ($iPos <= $iBufferEndIndex) {
|
||||
$sCharDef = $this->sResponseBuffer[$iPos];
|
||||
switch (true)
|
||||
{
|
||||
|
|
@ -272,8 +254,7 @@ trait ResponseParser
|
|||
++$iPos;
|
||||
break;
|
||||
case '[' === $sCharDef:
|
||||
if (null === $sAtomBuilder)
|
||||
{
|
||||
if (null === $sAtomBuilder) {
|
||||
$sAtomBuilder = '';
|
||||
}
|
||||
|
||||
|
|
@ -285,8 +266,7 @@ trait ResponseParser
|
|||
$oImapResponse, true, $sPreviousAtomUpperCase, '['
|
||||
);
|
||||
|
||||
if (null !== $sListBlock)
|
||||
{
|
||||
if (null !== $sListBlock) {
|
||||
$sAtomBuilder .= $sListBlock.']';
|
||||
}
|
||||
|
||||
|
|
@ -303,33 +283,25 @@ trait ResponseParser
|
|||
}
|
||||
}
|
||||
|
||||
if ($iPos > $iCharBlockStartPos || null !== $sAtomBuilder)
|
||||
{
|
||||
if ($iPos > $iCharBlockStartPos || null !== $sAtomBuilder) {
|
||||
$sLastCharBlock = \substr($this->sResponseBuffer, $iCharBlockStartPos, $iPos - $iCharBlockStartPos);
|
||||
if (null === $sAtomBuilder)
|
||||
{
|
||||
if (null === $sAtomBuilder) {
|
||||
$aList[] = 'NIL' === $sLastCharBlock ? null : $sLastCharBlock;
|
||||
$sPreviousAtomUpperCase = \strtoupper($sLastCharBlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sAtomBuilder .= $sLastCharBlock;
|
||||
|
||||
if (!$bTreatAsAtom)
|
||||
{
|
||||
if (!$bTreatAsAtom) {
|
||||
$aList[] = $sAtomBuilder;
|
||||
$sPreviousAtomUpperCase = \strtoupper($sAtomBuilder);
|
||||
$sAtomBuilder = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bRoot)
|
||||
{
|
||||
if (!isset($oImapResponse->Tag) && 1 === \count($aList))
|
||||
{
|
||||
if ($bRoot) {
|
||||
if (!isset($oImapResponse->Tag) && 1 === \count($aList)) {
|
||||
$oImapResponse->setTag($aList[0]);
|
||||
if ($this->getCurrentTag() === $oImapResponse->Tag)
|
||||
{
|
||||
if ($this->getCurrentTag() === $oImapResponse->Tag) {
|
||||
$oImapResponse->ResponseType = ResponseType::TAGGED;
|
||||
}
|
||||
}
|
||||
|
|
@ -374,8 +346,7 @@ trait ResponseParser
|
|||
|
||||
$iLiteralSize = \strlen($sLiteral);
|
||||
if ($iLiteralLen !== $iLiteralSize) {
|
||||
$this->writeLog('Literal stream read warning "read '.$iLiteralSize.' of '.
|
||||
$iLiteralLen.'" bytes', \LOG_WARNING);
|
||||
$this->writeLog('Literal stream read warning "read '.$iLiteralSize.' of '.$iLiteralLen.'" bytes', \LOG_WARNING);
|
||||
}
|
||||
return $sLiteral;
|
||||
}
|
||||
|
|
@ -387,9 +358,9 @@ trait ResponseParser
|
|||
}
|
||||
|
||||
$sLiteralAtomUpperCasePeek = '';
|
||||
if (0 === \strpos($sLiteralAtomUpperCase, 'BODY')) {
|
||||
if (\str_starts_with($sLiteralAtomUpperCase, 'BODY')) {
|
||||
$sLiteralAtomUpperCasePeek = \str_replace('BODY', 'BODY.PEEK', $sLiteralAtomUpperCase);
|
||||
} else if (0 === \strpos($sLiteralAtomUpperCase, 'BINARY')) {
|
||||
} else if (\str_starts_with($sLiteralAtomUpperCase, 'BINARY')) {
|
||||
$sLiteralAtomUpperCasePeek = \str_replace('BINARY', 'BINARY.PEEK', $sLiteralAtomUpperCase);
|
||||
}
|
||||
|
||||
|
|
@ -401,8 +372,7 @@ trait ResponseParser
|
|||
return false;
|
||||
}
|
||||
|
||||
$rImapLiteralStream =
|
||||
\MailSo\Base\StreamWrappers\Literal::CreateStream($this->ConnectionResource(), $iLiteralLen);
|
||||
$rImapLiteralStream = \MailSo\Base\StreamWrappers\Literal::CreateStream($this->ConnectionResource(), $iLiteralLen);
|
||||
|
||||
$this->writeLog('Start Callback for '.$sParent.' / '.$sLiteralAtomUpperCase.
|
||||
' - try to read '.$iLiteralLen.' bytes.', \LOG_INFO);
|
||||
|
|
@ -411,14 +381,14 @@ trait ResponseParser
|
|||
|
||||
try
|
||||
{
|
||||
$this->aFetchCallbacks[$sFetchKey]($sParent, $sLiteralAtomUpperCase, $rImapLiteralStream);
|
||||
$this->aFetchCallbacks[$sFetchKey]($sParent, $sLiteralAtomUpperCase, $rImapLiteralStream, $iLiteralLen);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$this->writeLogException($oException);
|
||||
$this->writeLogException($oException, \LOG_NOTICE, false);
|
||||
}
|
||||
|
||||
if ($rImapLiteralStream) {
|
||||
if (\is_resource($rImapLiteralStream)) {
|
||||
$iNotReadLiteralLen = 0;
|
||||
|
||||
$bFeof = \feof($rImapLiteralStream);
|
||||
|
|
@ -445,12 +415,10 @@ trait ResponseParser
|
|||
\fclose($rImapLiteralStream);
|
||||
|
||||
if (0 < $iNotReadLiteralLen) {
|
||||
$this->writeLog('Not read literal size is '.$iNotReadLiteralLen.' bytes.',
|
||||
\LOG_WARNING);
|
||||
$this->writeLog('Not read literal size is '.$iNotReadLiteralLen.' bytes.', \LOG_WARNING);
|
||||
}
|
||||
} else {
|
||||
$this->writeLog('Literal stream is not resource after callback.',
|
||||
\LOG_WARNING);
|
||||
$this->writeLog('Literal stream is not resource after callback.', \LOG_WARNING);
|
||||
}
|
||||
|
||||
$this->bRunningCallback = false;
|
||||
|
|
|
|||
|
|
@ -22,84 +22,94 @@ namespace MailSo\Imap\Traits;
|
|||
*/
|
||||
trait Status
|
||||
{
|
||||
public
|
||||
/**
|
||||
* The number of messages in the mailbox.
|
||||
* This response is named EXISTS as a result of a SELECT or EXAMINE command.
|
||||
* @var int
|
||||
*/
|
||||
$MESSAGES,
|
||||
public string $FullName;
|
||||
|
||||
/**
|
||||
* The number of messages with the \Recent flag set.
|
||||
* This response also occurs as a result of a SELECT or EXAMINE command.
|
||||
* IMAP4rev2 deprecated.
|
||||
* @var int
|
||||
*/
|
||||
$RECENT,
|
||||
public bool $hasStatus = false;
|
||||
|
||||
/**
|
||||
* The next unique identifier value of the mailbox.
|
||||
* A 32-bit value
|
||||
* This response also occurs as a result of a SELECT or EXAMINE command.
|
||||
* @var int
|
||||
*/
|
||||
$UIDNEXT,
|
||||
/**
|
||||
* The number of messages in the mailbox.
|
||||
* This response is named EXISTS as a result of a SELECT or EXAMINE command.
|
||||
*/
|
||||
public ?int $MESSAGES = null;
|
||||
|
||||
/**
|
||||
* The unique identifier validity value of the mailbox.
|
||||
* This response also occurs as a result of a SELECT or EXAMINE command.
|
||||
* @var int
|
||||
*/
|
||||
$UIDVALIDITY,
|
||||
/**
|
||||
* The number of messages with the \Recent flag set.
|
||||
* This response also occurs as a result of a SELECT or EXAMINE command.
|
||||
* IMAP4rev2 deprecated.
|
||||
*/
|
||||
public ?int $RECENT = null;
|
||||
|
||||
/**
|
||||
* The number of messages which do not have the \Seen flag set.
|
||||
* This response also occurs as a result of a IMAP4rev1 SELECT or EXAMINE command,
|
||||
* but then it is the message sequence number of the first unseen message.
|
||||
* IMAP4rev2 deprecated on SELECT/EXAMINE.
|
||||
* @var int
|
||||
*/
|
||||
$UNSEEN,
|
||||
/**
|
||||
* The next unique identifier value of the mailbox.
|
||||
* A 32-bit value
|
||||
* This response also occurs as a result of a SELECT or EXAMINE command.
|
||||
*/
|
||||
public ?int $UIDNEXT = null;
|
||||
|
||||
/**
|
||||
* RFC 4551
|
||||
* The highest mod-sequence value of all messages in the mailbox.
|
||||
* This response also occurs as a result of a SELECT or EXAMINE command.
|
||||
* @var int 1*DIGIT Positive unsigned 64-bit integer
|
||||
*/
|
||||
$HIGHESTMODSEQ,
|
||||
/**
|
||||
* The unique identifier validity value of the mailbox.
|
||||
* This response also occurs as a result of a SELECT or EXAMINE command.
|
||||
*/
|
||||
public ?int $UIDVALIDITY = null;
|
||||
|
||||
/**
|
||||
* RFC 7889
|
||||
* Message upload size limit.
|
||||
* @var int
|
||||
*/
|
||||
$APPENDLIMIT,
|
||||
/**
|
||||
* The number of messages which do not have the \Seen flag set.
|
||||
* This response also occurs as a result of a IMAP4rev1 SELECT or EXAMINE command,
|
||||
* but then it is the message sequence number of the first unseen message.
|
||||
* IMAP4rev2 deprecated on SELECT/EXAMINE.
|
||||
*/
|
||||
public ?int $UNSEEN = null;
|
||||
|
||||
/**
|
||||
* RFC 8474
|
||||
* A server-allocated unique identifier for the mailbox.
|
||||
* This response also occurs as a result of a CREATE, SELECT or EXAMINE command.
|
||||
* @var string
|
||||
*/
|
||||
$MAILBOXID,
|
||||
/**
|
||||
* RFC 4551
|
||||
* The highest mod-sequence value of all messages in the mailbox.
|
||||
* This response also occurs as a result of a SELECT or EXAMINE command.
|
||||
* 1*DIGIT Positive unsigned 64-bit integer
|
||||
*/
|
||||
public ?int $HIGHESTMODSEQ = null;
|
||||
|
||||
/**
|
||||
* RFC 9051
|
||||
* The total size of the mailbox in octets.
|
||||
* @var int
|
||||
*/
|
||||
$SIZE;
|
||||
/**
|
||||
* RFC 7889
|
||||
* Message upload size limit.
|
||||
*/
|
||||
public ?int $APPENDLIMIT = null;
|
||||
|
||||
public function getStatusItems() : array
|
||||
/**
|
||||
* RFC 8474
|
||||
* A server-allocated unique identifier for the mailbox.
|
||||
* This response also occurs as a result of a CREATE, SELECT or EXAMINE command.
|
||||
*/
|
||||
public ?string $MAILBOXID = null;
|
||||
|
||||
/**
|
||||
* RFC 9051
|
||||
* The total size of the mailbox in octets.
|
||||
*/
|
||||
public ?int $SIZE = null;
|
||||
|
||||
public ?string $etag = null;
|
||||
public function generateETag(\MailSo\Imap\ImapClient $oImapClient) : void
|
||||
{
|
||||
return \array_filter(\get_object_vars($this), function($v, $k){
|
||||
return \property_exists(__TRAIT__, $k);
|
||||
}, ARRAY_FILTER_USE_BOTH);
|
||||
if (!$this->hasStatus) {
|
||||
// UNSEEN undefined when only SELECT/EXAMINE is used
|
||||
\error_log("{$this->FullName} STATUS missing " . \print_r(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS),true));
|
||||
return;
|
||||
}
|
||||
if (!isset($this->MESSAGES, $this->UIDNEXT)) {
|
||||
return;
|
||||
}
|
||||
$this->etag = \md5('FolderHash/'. \implode('-', [
|
||||
$this->FullName,
|
||||
$this->MESSAGES,
|
||||
$this->UIDNEXT,
|
||||
$this->UIDVALIDITY,
|
||||
$this->UNSEEN,
|
||||
$this->HIGHESTMODSEQ,
|
||||
$oImapClient->Hash()
|
||||
]));
|
||||
}
|
||||
|
||||
public function setStatus(string $name, $value) : bool
|
||||
private function setStatusItem(string $name, $value) : bool
|
||||
{
|
||||
if ('EXISTS' === $name) {
|
||||
$name = 'MESSAGES';
|
||||
|
|
@ -107,10 +117,11 @@ trait Status
|
|||
$name = 'MAILBOXID';
|
||||
}
|
||||
if (\property_exists(__TRAIT__, $name)) {
|
||||
if ('MAILBOXID' !== $name) {
|
||||
$value = (int) $value;
|
||||
if ('MAILBOXID' === $name) {
|
||||
$this->MAILBOXID = \base64_encode(\is_array($value) ? $value[0] : $value);
|
||||
} else {
|
||||
$this->$name = (int) $value;
|
||||
}
|
||||
$this->$name = $value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -137,7 +148,7 @@ trait Status
|
|||
// OK untagged responses
|
||||
if (\is_array($oResponse->OptionalResponse)) {
|
||||
if (\count($oResponse->OptionalResponse) > 1) {
|
||||
$bResult = $this->setStatus($oResponse->OptionalResponse[0], $oResponse->OptionalResponse[1]);
|
||||
$bResult = $this->setStatusItem($oResponse->OptionalResponse[0], $oResponse->OptionalResponse[1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -149,16 +160,18 @@ trait Status
|
|||
&& \is_array($oResponse->ResponseList[3])) {
|
||||
$c = \count($oResponse->ResponseList[3]);
|
||||
for ($i = 0; $i < $c; $i += 2) {
|
||||
$bResult |= $this->setStatus(
|
||||
$bResult |= $this->setStatusItem(
|
||||
$oResponse->ResponseList[3][$i],
|
||||
$oResponse->ResponseList[3][$i+1]
|
||||
);
|
||||
}
|
||||
$this->hasStatus = $bResult;
|
||||
}
|
||||
// SELECT or EXAMINE command
|
||||
else if (\is_numeric($oResponse->ResponseList[1]) && \is_string($oResponse->ResponseList[2])) {
|
||||
// UNSEEN deprecated in IMAP4rev2
|
||||
if ('UNSEEN' !== $oResponse->ResponseList[2]) {
|
||||
$bResult |= $this->setStatus($oResponse->ResponseList[2], $oResponse->ResponseList[1]);
|
||||
$bResult |= $this->setStatusItem($oResponse->ResponseList[2], $oResponse->ResponseList[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,15 +17,9 @@ namespace MailSo\Log;
|
|||
*/
|
||||
abstract class Driver
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sDatePattern = 'H:i:s';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sName = '';
|
||||
protected string
|
||||
$sDatePattern = 'H:i:s',
|
||||
$sName = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
|
|
@ -41,25 +35,13 @@ abstract class Driver
|
|||
\LOG_DEBUG => '[DEBUG]'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $bGuidPrefix = true;
|
||||
protected bool $bGuidPrefix = true;
|
||||
|
||||
/**
|
||||
* @var DateTimeZone
|
||||
*/
|
||||
protected $oTimeZone;
|
||||
protected \DateTimeZone $oTimeZone;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $bTimePrefix = true;
|
||||
protected bool $bTimePrefix = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $bTypedPrefix = true;
|
||||
protected bool $bTypedPrefix = true;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,19 +18,12 @@ namespace MailSo\Log\Drivers;
|
|||
*/
|
||||
class File extends \MailSo\Log\Driver
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sLoggerFileName;
|
||||
private string $sLoggerFileName;
|
||||
|
||||
function __construct(string $sLoggerFileName)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->SetLoggerFileName($sLoggerFileName);
|
||||
}
|
||||
|
||||
public function SetLoggerFileName(string $sLoggerFileName)
|
||||
{
|
||||
$sLogFileDir = \dirname($sLoggerFileName);
|
||||
if (!\is_dir($sLogFileDir)) {
|
||||
\mkdir($sLogFileDir, 0755, true);
|
||||
|
|
@ -40,19 +33,14 @@ class File extends \MailSo\Log\Driver
|
|||
|
||||
protected function writeImplementation($mDesc) : bool
|
||||
{
|
||||
return $this->writeToLogFile($mDesc);
|
||||
if (\is_array($mDesc)) {
|
||||
$mDesc = \implode("\n\t", $mDesc);
|
||||
}
|
||||
return \error_log($mDesc . "\n", 3, $this->sLoggerFileName);
|
||||
}
|
||||
|
||||
protected function clearImplementation() : bool
|
||||
{
|
||||
return \unlink($this->sLoggerFileName);
|
||||
}
|
||||
|
||||
private function writeToLogFile($mDesc) : bool
|
||||
{
|
||||
if (\is_array($mDesc)) {
|
||||
$mDesc = \implode("\n\t", $mDesc);
|
||||
}
|
||||
return \error_log($mDesc . "\n", 3, $this->sLoggerFileName);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2023 DJMaze
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo\Log\Drivers;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Log
|
||||
* @subpackage Drivers
|
||||
*/
|
||||
class StderrStream extends \MailSo\Log\Driver
|
||||
{
|
||||
protected function writeImplementation($mDesc) : bool
|
||||
{
|
||||
if (!\defined('STDERR')) {
|
||||
\define('STDERR', \fopen('php://stderr', 'wb'));
|
||||
}
|
||||
return 0 < \fwrite(STDERR, $mDesc . "\n");
|
||||
}
|
||||
|
||||
protected function clearImplementation() : bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ namespace MailSo\Log\Drivers;
|
|||
*/
|
||||
class Syslog extends \MailSo\Log\Driver
|
||||
{
|
||||
private $iLogLevel;
|
||||
private int $iLogLevel = 0;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
|
|
|
|||
52
snappymail/v/0.0.0/app/libraries/MailSo/Log/Inherit.php
Normal file
52
snappymail/v/0.0.0/app/libraries/MailSo/Log/Inherit.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2014 Usenko Timur
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo\Log;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Log
|
||||
*/
|
||||
trait Inherit
|
||||
{
|
||||
protected ?Logger $oLogger = null;
|
||||
|
||||
public function Logger(): ?Logger
|
||||
{
|
||||
return $this->oLogger;
|
||||
}
|
||||
|
||||
public function SetLogger(?Logger $oLogger): void
|
||||
{
|
||||
$this->oLogger = $oLogger;
|
||||
}
|
||||
|
||||
public function logWrite(string $sDesc, int $iType = \LOG_INFO,
|
||||
string $sName = '', bool $bSearchSecretWords = true, bool $bDiplayCrLf = false): bool
|
||||
{
|
||||
return $this->oLogger && $this->oLogger->Write($sDesc, $iType, $sName, $bSearchSecretWords, $bDiplayCrLf);
|
||||
}
|
||||
|
||||
public function logException(\Throwable $oException, int $iType = \LOG_NOTICE, string $sName = ''): void
|
||||
{
|
||||
$this->oLogger && $this->oLogger->WriteException($oException, $iType, $sName);
|
||||
}
|
||||
|
||||
public function logMask(string $sWord): void
|
||||
{
|
||||
$this->oLogger && $this->oLogger->AddSecret($sWord);
|
||||
}
|
||||
|
||||
/*
|
||||
public function logWriteDump($mValue, int $iType = \LOG_INFO, string $sName = '') : bool
|
||||
public function logWriteExceptionShort(\Throwable $oException, int $iType = \LOG_NOTICE, string $sName = '') : void
|
||||
*/
|
||||
}
|
||||
|
|
@ -17,23 +17,67 @@ namespace MailSo\Log;
|
|||
*/
|
||||
class Logger extends \SplFixedArray
|
||||
{
|
||||
private $bUsed = false;
|
||||
private bool $bUsed = false;
|
||||
|
||||
private $iLevel = \LOG_WARNING;
|
||||
private int $iLevel = \LOG_WARNING;
|
||||
|
||||
private $aSecretWords = [];
|
||||
private array $aSecretWords = [];
|
||||
|
||||
private $bShowSecrets = false;
|
||||
private bool $bShowSecrets = false;
|
||||
|
||||
private static $SIGNALS = [
|
||||
'SIGABRT',
|
||||
'SIGALRM',
|
||||
'SIGBABY',
|
||||
'SIGBUS',
|
||||
'SIGCHLD',
|
||||
'SIGCLD',
|
||||
// 'SIGCONT',
|
||||
'SIGFPE',
|
||||
'SIGHUP',
|
||||
'SIGILL',
|
||||
// 'SIGINT',
|
||||
'SIGIO',
|
||||
'SIGIOT',
|
||||
// 'SIGKILL',
|
||||
'SIGPIPE',
|
||||
'SIGPOLL',
|
||||
'SIGPROF',
|
||||
'SIGPWR',
|
||||
'SIGQUIT',
|
||||
'SIGSEGV',
|
||||
'SIGSTKFLT',
|
||||
// 'SIGSTOP',
|
||||
'SIGSYS',
|
||||
'SIGTERM',
|
||||
'SIGTRAP',
|
||||
'SIGTSTP',
|
||||
'SIGTTIN',
|
||||
'SIGTTOU',
|
||||
'SIGURG',
|
||||
'SIGUSR1',
|
||||
'SIGUSR2',
|
||||
'SIGVTALRM',
|
||||
'SIGWINCH',
|
||||
'SIGXCPU',
|
||||
'SIGXFSZ'
|
||||
];
|
||||
|
||||
function __construct(bool $bMainLogger = false)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if ($bMainLogger)
|
||||
{
|
||||
\set_error_handler(array($this, '__phpErrorHandler'));
|
||||
\set_exception_handler(array($this, '__phpExceptionHandler'));
|
||||
\register_shutdown_function(array($this, '__loggerShutDown'));
|
||||
if ($bMainLogger) {
|
||||
\set_error_handler([$this, '__phpErrorHandler']);
|
||||
\set_exception_handler([$this, '__phpExceptionHandler']);
|
||||
\register_shutdown_function([$this, '__loggerShutDown']);
|
||||
|
||||
if (\is_callable('pcntl_signal')) {
|
||||
\pcntl_async_signals(true);
|
||||
foreach (static::$SIGNALS as $SIGNAL) {
|
||||
\defined($SIGNAL) && \pcntl_signal(\constant($SIGNAL), [$this, 'signalHandler']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -43,8 +87,7 @@ class Logger extends \SplFixedArray
|
|||
public static function Guid() : string
|
||||
{
|
||||
static $sCache = null;
|
||||
if (null === $sCache)
|
||||
{
|
||||
if (null === $sCache) {
|
||||
$sCache = \substr(\MailSo\Base\Utils::Sha1Rand(), -8);
|
||||
}
|
||||
|
||||
|
|
@ -152,11 +195,28 @@ class Logger extends \SplFixedArray
|
|||
public function __loggerShutDown() : void
|
||||
{
|
||||
if ($this->bUsed) {
|
||||
$error = \error_get_last();
|
||||
$error && $this->Write('Last error: '.\json_encode($error));
|
||||
$this->Write('Memory peak usage: '.\MailSo\Base\Utils::FormatFileSize(\memory_get_peak_usage(true), 2));
|
||||
$this->Write('Time delta: '.(\microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']));
|
||||
}
|
||||
}
|
||||
|
||||
public function signalHandler($signo)
|
||||
{
|
||||
if (\SIGTERM == $signo) {
|
||||
exit;
|
||||
}
|
||||
if ($this->bUsed) {
|
||||
foreach (static::$SIGNALS as $SIGNAL) {
|
||||
if (\defined($SIGNAL) && \constant($SIGNAL) == $signo) {
|
||||
$this->Write("Caught {$SIGNAL}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function Write(string $sDesc, int $iType = \LOG_INFO,
|
||||
string $sName = '', bool $bSearchSecretWords = true, bool $bDiplayCrLf = false) : bool
|
||||
{
|
||||
|
|
@ -166,8 +226,7 @@ class Logger extends \SplFixedArray
|
|||
|
||||
$this->bUsed = true;
|
||||
|
||||
if ($bSearchSecretWords && !$this->bShowSecrets && $this->aSecretWords)
|
||||
{
|
||||
if ($bSearchSecretWords && !$this->bShowSecrets && $this->aSecretWords) {
|
||||
$sDesc = \str_replace($this->aSecretWords, '*******', $sDesc);
|
||||
}
|
||||
|
||||
|
|
@ -189,19 +248,21 @@ class Logger extends \SplFixedArray
|
|||
return $this->Write(\print_r($mValue, true), $iType, $sName);
|
||||
}
|
||||
|
||||
private $aExceptions = [];
|
||||
|
||||
public function WriteException(\Throwable $oException, int $iType = \LOG_NOTICE, string $sName = '') : void
|
||||
{
|
||||
if (empty($oException->__WRITTEN__)) {
|
||||
$oException->__WRITTEN__ = true;
|
||||
if (!\in_array($oException, $this->aExceptions)) {
|
||||
$this->Write((string) $oException, $iType, $sName);
|
||||
$this->aExceptions[] = $oException;
|
||||
}
|
||||
}
|
||||
|
||||
public function WriteExceptionShort(\Throwable $oException, int $iType = \LOG_NOTICE, string $sName = '') : void
|
||||
{
|
||||
if (empty($oException->__WRITTEN__)) {
|
||||
$oException->__WRITTEN__ = true;
|
||||
if (!\in_array($oException, $this->aExceptions)) {
|
||||
$this->Write($oException->getMessage(), $iType, $sName);
|
||||
$this->aExceptions[] = $oException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,30 +11,25 @@
|
|||
|
||||
namespace MailSo\Mail;
|
||||
|
||||
use MailSo\Imap\BodyStructure;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Mail
|
||||
*/
|
||||
class Attachment implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sFolder;
|
||||
private string $sFolder;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iUid;
|
||||
private int $iUid;
|
||||
|
||||
/**
|
||||
* @var \MailSo\Imap\BodyStructure
|
||||
*/
|
||||
private $oBodyStructure;
|
||||
private ?BodyStructure $oBodyStructure;
|
||||
|
||||
function __construct()
|
||||
function __construct(string $sFolder, int $iUid, BodyStructure $oBodyStructure)
|
||||
{
|
||||
$this->Clear();
|
||||
$this->sFolder = $sFolder;
|
||||
$this->iUid = $iUid;
|
||||
$this->oBodyStructure = $oBodyStructure;
|
||||
}
|
||||
|
||||
public function Clear() : self
|
||||
|
|
@ -56,127 +51,18 @@ class Attachment implements \JsonSerializable
|
|||
return $this->iUid;
|
||||
}
|
||||
|
||||
public function MimeIndex() : string
|
||||
public function __call(string $name, array $arguments) //: mixed
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->PartID() : '';
|
||||
}
|
||||
|
||||
public function FileName(bool $bCalculateOnEmpty = false) : string
|
||||
{
|
||||
if (!$this->oBodyStructure) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$sFileName = \trim($this->oBodyStructure->FileName());
|
||||
if (\strlen($sFileName) || !$bCalculateOnEmpty) {
|
||||
return $sFileName;
|
||||
}
|
||||
|
||||
$sIdx = '-' . $this->MimeIndex();
|
||||
|
||||
$sMimeType = \strtolower(\trim($this->MimeType()));
|
||||
if ('message/rfc822' === $sMimeType) {
|
||||
return "message{$sIdx}.eml";
|
||||
}
|
||||
if ('text/calendar' === $sMimeType) {
|
||||
return "calendar{$sIdx}.ics";
|
||||
}
|
||||
if ('text/plain' === $sMimeType) {
|
||||
return "part{$sIdx}.txt";
|
||||
}
|
||||
if (\preg_match('@text/(vcard|html|csv|xml|css|asp)@', $sMimeType, $aMatch)
|
||||
|| \preg_match('@image/(png|jpeg|gif|bmp|cgm|ief|tiff|webp)@', $sMimeType, $aMatch)) {
|
||||
return "part{$sIdx}.{$aMatch[1]}";
|
||||
}
|
||||
if (\strlen($sMimeType)) {
|
||||
return \str_replace('/', $sIdx.'.', $sMimeType);
|
||||
}
|
||||
|
||||
return ($this->oBodyStructure->IsInline() ? 'inline' : 'part' ) . $sIdx;
|
||||
}
|
||||
|
||||
public function MimeType() : string
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->ContentType() : '';
|
||||
}
|
||||
|
||||
public function EncodedSize() : int
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->Size() : 0;
|
||||
}
|
||||
|
||||
public function EstimatedSize() : int
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->EstimatedSize() : 0;
|
||||
}
|
||||
|
||||
public function Cid() : string
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->ContentID() : '';
|
||||
}
|
||||
|
||||
public function ContentLocation() : string
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->ContentLocation() : '';
|
||||
}
|
||||
|
||||
public function IsInline() : bool
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->IsInline() : false;
|
||||
}
|
||||
|
||||
public function IsImage() : bool
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->IsImage() : false;
|
||||
}
|
||||
|
||||
public function IsArchive() : bool
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->IsArchive() : false;
|
||||
}
|
||||
|
||||
public function IsPdf() : bool
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->IsPdf() : false;
|
||||
}
|
||||
|
||||
public function IsDoc() : bool
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->IsDoc() : false;
|
||||
}
|
||||
|
||||
public function IsPgpSignature() : bool
|
||||
{
|
||||
return $this->oBodyStructure ? $this->oBodyStructure->IsPgpSignature() : false;
|
||||
}
|
||||
|
||||
public static function NewBodyStructureInstance(string $sFolder, int $iUid, \MailSo\Imap\BodyStructure $oBodyStructure) : self
|
||||
{
|
||||
return (new self)->InitByBodyStructure($sFolder, $iUid, $oBodyStructure);
|
||||
}
|
||||
|
||||
public function InitByBodyStructure(string $sFolder, int $iUid, \MailSo\Imap\BodyStructure $oBodyStructure) : self
|
||||
{
|
||||
$this->sFolder = $sFolder;
|
||||
$this->iUid = $iUid;
|
||||
$this->oBodyStructure = $oBodyStructure;
|
||||
return $this;
|
||||
return $this->oBodyStructure->{$name}(...$arguments);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return array(
|
||||
return \array_merge([
|
||||
'@Object' => 'Object/Attachment',
|
||||
'Folder' => $this->Folder(),
|
||||
'Uid' => (string) $this->Uid(),
|
||||
'MimeIndex' => (string) $this->MimeIndex(),
|
||||
'MimeType' => $this->MimeType(),
|
||||
'FileName' => \MailSo\Base\Utils::SecureFileName($this->FileName(true)),
|
||||
'EstimatedSize' => $this->EstimatedSize(),
|
||||
'Cid' => $this->Cid(),
|
||||
'ContentLocation' => $this->ContentLocation(),
|
||||
'IsInline' => $this->IsInline()
|
||||
);
|
||||
'folder' => $this->sFolder,
|
||||
'uid' => $this->iUid
|
||||
], $this->oBodyStructure->jsonSerialize());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,240 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2014 Usenko Timur
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo\Mail;
|
||||
|
||||
use MailSo\Imap\Enumerations\FolderType;
|
||||
use MailSo\Imap\Enumerations\MetadataKeys;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Mail
|
||||
*/
|
||||
class Folder implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bExists;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bSubscribed;
|
||||
|
||||
/**
|
||||
* @var \MailSo\Imap\Folder
|
||||
*/
|
||||
private $oImapFolder;
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
function __construct(\MailSo\Imap\Folder $oImapFolder, bool $bSubscribed = true, bool $bExists = true)
|
||||
{
|
||||
$this->oImapFolder = $oImapFolder;
|
||||
$this->bSubscribed = $bSubscribed || \in_array('\\subscribed', $oImapFolder->FlagsLowerCase());
|
||||
$this->bExists = $bExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function NewNonExistentInstance(string $sFullName, string $sDelimiter) : self
|
||||
{
|
||||
return new self(
|
||||
new \MailSo\Imap\Folder($sFullName, $sDelimiter, array('\\Noselect')), false, false);
|
||||
}
|
||||
|
||||
public function Name() : string
|
||||
{
|
||||
return $this->oImapFolder->Name();
|
||||
}
|
||||
|
||||
public function FullName() : string
|
||||
{
|
||||
return $this->oImapFolder->FullName();
|
||||
}
|
||||
|
||||
public function NameRaw() : string
|
||||
{
|
||||
return $this->oImapFolder->NameRaw();
|
||||
}
|
||||
|
||||
public function Delimiter() : string
|
||||
{
|
||||
return $this->oImapFolder->Delimiter();
|
||||
}
|
||||
|
||||
public function FlagsLowerCase() : array
|
||||
{
|
||||
return $this->oImapFolder->FlagsLowerCase();
|
||||
}
|
||||
|
||||
public function IsSubscribed() : bool
|
||||
{
|
||||
return $this->bSubscribed;
|
||||
}
|
||||
|
||||
public function IsExists() : bool
|
||||
{
|
||||
return $this->bExists;
|
||||
}
|
||||
|
||||
public function IsSelectable() : bool
|
||||
{
|
||||
return $this->bExists && $this->oImapFolder->IsSelectable();
|
||||
}
|
||||
|
||||
public function Status() : array
|
||||
{
|
||||
return $this->oImapFolder->getStatusItems();
|
||||
}
|
||||
|
||||
public function IsInbox() : bool
|
||||
{
|
||||
return $this->oImapFolder->IsInbox();
|
||||
}
|
||||
|
||||
public function GetFolderListType() : int
|
||||
{
|
||||
$aFlags = $this->oImapFolder->FlagsLowerCase();
|
||||
// RFC 6154
|
||||
// $aFlags[] = \strtolower($this->oImapFolder->GetMetadata(MetadataKeys::SPECIALUSE));
|
||||
|
||||
switch (true)
|
||||
{
|
||||
case \in_array('\\inbox', $aFlags) || 'INBOX' === \strtoupper($this->FullName()):
|
||||
return FolderType::INBOX;
|
||||
|
||||
case \in_array('\\sent', $aFlags):
|
||||
case \in_array('\\sentmail', $aFlags):
|
||||
return FolderType::SENT;
|
||||
|
||||
case \in_array('\\drafts', $aFlags):
|
||||
return FolderType::DRAFTS;
|
||||
|
||||
case \in_array('\\junk', $aFlags):
|
||||
case \in_array('\\spam', $aFlags):
|
||||
return FolderType::JUNK;
|
||||
|
||||
case \in_array('\\trash', $aFlags):
|
||||
case \in_array('\\bin', $aFlags):
|
||||
return FolderType::TRASH;
|
||||
|
||||
case \in_array('\\important', $aFlags):
|
||||
return FolderType::IMPORTANT;
|
||||
|
||||
case \in_array('\\flagged', $aFlags):
|
||||
case \in_array('\\starred', $aFlags):
|
||||
return FolderType::FLAGGED;
|
||||
|
||||
case \in_array('\\archive', $aFlags):
|
||||
return FolderType::ARCHIVE;
|
||||
|
||||
case \in_array('\\all', $aFlags):
|
||||
case \in_array('\\allmail', $aFlags):
|
||||
return FolderType::ALL;
|
||||
|
||||
// TODO
|
||||
// case 'Templates' === $this->FullName():
|
||||
// return FolderType::TEMPLATES;
|
||||
}
|
||||
|
||||
// Kolab
|
||||
$type = $this->GetMetadata(MetadataKeys::KOLAB_CTYPE) ?: $this->GetMetadata(MetadataKeys::KOLAB_CTYPE_SHARED);
|
||||
switch ($type)
|
||||
{
|
||||
/*
|
||||
// TODO: Kolab
|
||||
case 'event':
|
||||
case 'event.default':
|
||||
return FolderType::CALENDAR;
|
||||
case 'contact':
|
||||
case 'contact.default':
|
||||
return FolderType::CONTACTS;
|
||||
case 'task':
|
||||
case 'task.default':
|
||||
return FolderType::TASKS;
|
||||
case 'note':
|
||||
case 'note.default':
|
||||
return FolderType::NOTES;
|
||||
case 'file':
|
||||
case 'file.default':
|
||||
return FolderType::FILES;
|
||||
case 'configuration':
|
||||
return FolderType::CONFIGURATION;
|
||||
case 'journal':
|
||||
case 'journal.default':
|
||||
return FolderType::JOURNAL;
|
||||
*/
|
||||
case 'mail.inbox':
|
||||
return FolderType::INBOX;
|
||||
// case 'mail.outbox':
|
||||
case 'mail.sentitems':
|
||||
return FolderType::SENT;
|
||||
case 'mail.drafts':
|
||||
return FolderType::DRAFTS;
|
||||
case 'mail.junkemail':
|
||||
return FolderType::JUNK;
|
||||
case 'mail.wastebasket':
|
||||
return FolderType::TRASH;
|
||||
}
|
||||
|
||||
return FolderType::USER;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetMetadata(string $sName) : ?string
|
||||
{
|
||||
return $this->oImapFolder->GetMetadata($sName);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
/*
|
||||
$aExtended = null;
|
||||
$aStatus = $this->oImapFolder->getStatusItems();
|
||||
if ($aStatus && isset($aStatus['MESSAGES'], $aStatus['UNSEEN'], $aStatus['UIDNEXT'])) {
|
||||
$aExtended = array(
|
||||
'totalEmails' => (int) $aStatus['MESSAGES'],
|
||||
'unreadEmails' => (int) $aStatus['UNSEEN'],
|
||||
'UidNext' => (int) $aStatus['UIDNEXT'],
|
||||
// 'Hash' => $this->MailClient()->GenerateFolderHash(
|
||||
// $this->FullName(), $aStatus['MESSAGES'], $aStatus['UIDNEXT'],
|
||||
// empty($aStatus['HIGHESTMODSEQ']) ? 0 : $aStatus['HIGHESTMODSEQ'])
|
||||
);
|
||||
}
|
||||
*/
|
||||
return array(
|
||||
'@Object' => 'Object/Folder',
|
||||
'Name' => $this->Name(),
|
||||
'FullName' => $this->FullName(),
|
||||
'Delimiter' => (string) $this->Delimiter(),
|
||||
'isSubscribed' => $this->bSubscribed,
|
||||
'Exists' => $this->bExists,
|
||||
'Selectable' => $this->IsSelectable(),
|
||||
'Flags' => $this->FlagsLowerCase(),
|
||||
// 'Extended' => $aExtended,
|
||||
// 'PermanentFlags' => $this->oImapFolder->PermanentFlags,
|
||||
'Metadata' => $this->oImapFolder->Metadata(),
|
||||
'UidNext' => $this->oImapFolder->UIDNEXT,
|
||||
// RFC 8621
|
||||
'totalEmails' => $this->oImapFolder->MESSAGES,
|
||||
'unreadEmails' => $this->oImapFolder->UNSEEN,
|
||||
'id' => $this->oImapFolder->MAILBOXID
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
namespace MailSo\Mail;
|
||||
|
||||
use \MailSo\Base\Utils;
|
||||
use \MailSo\Imap\Enumerations\FetchType;
|
||||
use MailSo\Base\Utils;
|
||||
use MailSo\Imap\Enumerations\FetchType;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
|
|
@ -20,114 +20,77 @@ use \MailSo\Imap\Enumerations\FetchType;
|
|||
*/
|
||||
class Message implements \JsonSerializable
|
||||
{
|
||||
private
|
||||
private string
|
||||
$sFolder = '',
|
||||
$iUid = 0,
|
||||
$sSubject = '',
|
||||
$sMessageId = '',
|
||||
$sContentType = '',
|
||||
$iSize = 0,
|
||||
$iSpamScore = 0,
|
||||
$sSpamResult = '',
|
||||
$bIsSpam = false,
|
||||
$bHasVirus = null,
|
||||
$iInternalTimeStampInUTC = 0,
|
||||
$iHeaderTimeStampInUTC = 0,
|
||||
$sHeaderDate = '',
|
||||
// $aFlags = [],
|
||||
$aFlagsLowerCase = [],
|
||||
$sVirusScanned = '',
|
||||
$InReplyTo = '',
|
||||
$sPlain = '',
|
||||
$sHtml = '',
|
||||
$References = '',
|
||||
$sDeliveryReceipt = '',
|
||||
$ReadReceipt = '';
|
||||
|
||||
private ?string
|
||||
/**
|
||||
* https://www.rfc-editor.org/rfc/rfc8474#section-5
|
||||
*/
|
||||
$sEmailId = '',
|
||||
$sThreadId = '',
|
||||
$sEmailId = null,
|
||||
$sThreadId = null,
|
||||
// https://autocrypt.org/level1.html#the-autocrypt-header
|
||||
$sAutocrypt = '';
|
||||
|
||||
private int
|
||||
$Uid = 0,
|
||||
$iSize = 0,
|
||||
$SpamScore = 0,
|
||||
$iInternalTimeStampInUTC = 0,
|
||||
$iHeaderTimeStampInUTC = 0,
|
||||
$iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL;
|
||||
|
||||
private bool
|
||||
$bIsSpam = false;
|
||||
|
||||
private ?bool
|
||||
/**
|
||||
* @var \MailSo\Mime\EmailCollection
|
||||
* null = not scanned
|
||||
* true = scanned and infected
|
||||
* false = scanned and no infection found
|
||||
*/
|
||||
$bHasVirus = null;
|
||||
|
||||
private array
|
||||
$SPF = [],
|
||||
$DKIM = [],
|
||||
$DMARC = [],
|
||||
// $aFlags = [],
|
||||
$aFlagsLowerCase = [],
|
||||
$UnsubsribeLinks = [],
|
||||
$aThreadUIDs = [],
|
||||
$aThreadUnseenUIDs = [];
|
||||
|
||||
private ?array $DraftInfo = null;
|
||||
private ?array $pgpSigned = null;
|
||||
private ?array $pgpEncrypted = null;
|
||||
|
||||
private ?\MailSo\Mime\EmailCollection
|
||||
$oFrom = null,
|
||||
$oSender = null,
|
||||
$oReplyTo = null,
|
||||
$oDeliveredTo = null,
|
||||
$oTo = null,
|
||||
$oCc = null,
|
||||
$oBcc = null,
|
||||
$oBcc = null;
|
||||
|
||||
$sInReplyTo = '',
|
||||
private ?AttachmentCollection
|
||||
$Attachments = null;
|
||||
|
||||
$sPlain = '',
|
||||
$sHtml = '',
|
||||
|
||||
/**
|
||||
* @var AttachmentCollection
|
||||
*/
|
||||
$oAttachments = null,
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
$aDraftInfo = null,
|
||||
|
||||
$sReferences = '',
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
$iPriority,
|
||||
|
||||
$sDeliveryReceipt = '',
|
||||
|
||||
$sReadReceipt = '',
|
||||
|
||||
// https://autocrypt.org/level1.html#the-autocrypt-header
|
||||
$sAutocrypt = '',
|
||||
|
||||
$aUnsubsribeLinks = array(),
|
||||
|
||||
$aThreads = array(),
|
||||
|
||||
$aPgpSigned = null,
|
||||
$aPgpEncrypted = null;
|
||||
|
||||
function __construct()
|
||||
function __get($k)
|
||||
{
|
||||
$this->iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL;
|
||||
}
|
||||
|
||||
public function Plain() : string
|
||||
{
|
||||
return $this->sPlain;
|
||||
}
|
||||
|
||||
public function Html() : string
|
||||
{
|
||||
return $this->sHtml;
|
||||
}
|
||||
|
||||
public function PgpSigned() : ?array
|
||||
{
|
||||
return $this->aPgpSigned;
|
||||
}
|
||||
|
||||
public function PgpEncrypted() : ?array
|
||||
{
|
||||
return $this->aPgpEncrypted;
|
||||
}
|
||||
|
||||
public function Folder() : string
|
||||
{
|
||||
return $this->sFolder;
|
||||
}
|
||||
|
||||
public function Uid() : int
|
||||
{
|
||||
return $this->iUid;
|
||||
}
|
||||
|
||||
public function MessageId() : string
|
||||
{
|
||||
return $this->sMessageId;
|
||||
return \property_exists($this, $k) ? $this->$k : null;
|
||||
}
|
||||
|
||||
public function Subject() : string
|
||||
|
|
@ -135,164 +98,48 @@ class Message implements \JsonSerializable
|
|||
return $this->sSubject;
|
||||
}
|
||||
|
||||
public function ContentType() : string
|
||||
{
|
||||
return $this->sContentType;
|
||||
}
|
||||
|
||||
public function Size() : int
|
||||
{
|
||||
return $this->iSize;
|
||||
}
|
||||
|
||||
public function SpamScore() : int
|
||||
{
|
||||
return $this->iSpamScore;
|
||||
}
|
||||
|
||||
private function setSpamScore($value) : void
|
||||
{
|
||||
$this->iSpamScore = \intval(\max(0, \min(100, $value)));
|
||||
}
|
||||
|
||||
public function SpamResult() : string
|
||||
{
|
||||
return $this->sSpamResult;
|
||||
}
|
||||
|
||||
public function IsSpam() : bool
|
||||
{
|
||||
return $this->bIsSpam;
|
||||
}
|
||||
|
||||
/**
|
||||
* null = not scanned
|
||||
* true = scanned and infected
|
||||
* false = scanned and no infection found
|
||||
*/
|
||||
public function HasVirus() : ?bool
|
||||
{
|
||||
return $this->bHasVirus;
|
||||
}
|
||||
|
||||
public function InternalTimeStampInUTC() : int
|
||||
{
|
||||
return $this->iInternalTimeStampInUTC;
|
||||
}
|
||||
|
||||
public function HeaderTimeStampInUTC() : int
|
||||
{
|
||||
return $this->iHeaderTimeStampInUTC;
|
||||
}
|
||||
|
||||
public function HeaderDate() : string
|
||||
{
|
||||
return $this->sHeaderDate;
|
||||
}
|
||||
|
||||
public function From() : ?\MailSo\Mime\EmailCollection
|
||||
{
|
||||
return $this->oFrom;
|
||||
}
|
||||
|
||||
public function Priority() : int
|
||||
public function Uid() : int
|
||||
{
|
||||
return $this->iPriority;
|
||||
}
|
||||
|
||||
public function Sender() : ?\MailSo\Mime\EmailCollection
|
||||
{
|
||||
return $this->oSender;
|
||||
}
|
||||
|
||||
public function ReplyTo() : ?\MailSo\Mime\EmailCollection
|
||||
{
|
||||
return $this->oReplyTo;
|
||||
}
|
||||
|
||||
public function DeliveredTo() : ?\MailSo\Mime\EmailCollection
|
||||
{
|
||||
return $this->oDeliveredTo;
|
||||
}
|
||||
|
||||
public function To() : ?\MailSo\Mime\EmailCollection
|
||||
{
|
||||
return $this->oTo;
|
||||
}
|
||||
|
||||
public function Cc() : ?\MailSo\Mime\EmailCollection
|
||||
{
|
||||
return $this->oCc;
|
||||
}
|
||||
|
||||
public function Bcc() : ?\MailSo\Mime\EmailCollection
|
||||
{
|
||||
return $this->oBcc;
|
||||
return $this->Uid;
|
||||
}
|
||||
|
||||
public function Attachments() : ?AttachmentCollection
|
||||
{
|
||||
return $this->oAttachments;
|
||||
return $this->Attachments;
|
||||
}
|
||||
|
||||
public function InReplyTo() : string
|
||||
private function setSpamScore($value) : void
|
||||
{
|
||||
return $this->sInReplyTo;
|
||||
$this->SpamScore = \intval(\max(0, \min(100, $value)));
|
||||
}
|
||||
|
||||
public function References() : string
|
||||
public function SetThreads(array $aThreadUIDs)
|
||||
{
|
||||
return $this->sReferences;
|
||||
$this->aThreadUIDs = $aThreadUIDs;
|
||||
}
|
||||
|
||||
public function DeliveryReceipt() : string
|
||||
public function SetThreadUnseen(array $aUnseenUIDs)
|
||||
{
|
||||
return $this->sDeliveryReceipt;
|
||||
$this->aThreadUnseenUIDs = $aUnseenUIDs;
|
||||
}
|
||||
|
||||
public function ReadReceipt() : string
|
||||
{
|
||||
return $this->sReadReceipt;
|
||||
}
|
||||
|
||||
public function UnsubsribeLinks() : array
|
||||
{
|
||||
return $this->aUnsubsribeLinks;
|
||||
}
|
||||
|
||||
public function ReadingConfirmation() : string
|
||||
{
|
||||
return $this->ReadReceipt();
|
||||
}
|
||||
|
||||
public function DraftInfo() : ?array
|
||||
{
|
||||
return $this->aDraftInfo;
|
||||
}
|
||||
|
||||
public function Threads() : array
|
||||
{
|
||||
return $this->aThreads;
|
||||
}
|
||||
|
||||
public function SetThreads(array $aThreads)
|
||||
{
|
||||
$this->aThreads = $aThreads;
|
||||
}
|
||||
|
||||
public static function NewFetchResponseInstance(string $sFolder, \MailSo\Imap\FetchResponse $oFetchResponse, ?\MailSo\Imap\BodyStructure $oBodyStructure = null) : self
|
||||
public static function fromFetchResponse(string $sFolder, \MailSo\Imap\FetchResponse $oFetchResponse, ?\MailSo\Imap\BodyStructure $oBodyStructure = null) : self
|
||||
{
|
||||
$oMessage = new self;
|
||||
|
||||
if (!$oBodyStructure)
|
||||
{
|
||||
if (!$oBodyStructure) {
|
||||
$oBodyStructure = $oFetchResponse->GetFetchBodyStructure();
|
||||
}
|
||||
|
||||
$aFlags = $oFetchResponse->GetFetchValue(FetchType::FLAGS) ?: [];
|
||||
|
||||
$oMessage->sFolder = $sFolder;
|
||||
$oMessage->iUid = (int) $oFetchResponse->GetFetchValue(FetchType::UID);
|
||||
$oMessage->Uid = (int) $oFetchResponse->GetFetchValue(FetchType::UID);
|
||||
$oMessage->iSize = (int) $oFetchResponse->GetFetchValue(FetchType::RFC822_SIZE);
|
||||
// $oMessage->aFlags = $aFlags;
|
||||
$oMessage->aFlagsLowerCase = \array_map('mb_strtolower', \array_map('\\MailSo\\Base\\Utils::Utf7ModifiedToUtf8', $aFlags));
|
||||
|
|
@ -300,31 +147,28 @@ class Message implements \JsonSerializable
|
|||
$oFetchResponse->GetFetchValue(FetchType::INTERNALDATE)
|
||||
);
|
||||
|
||||
$oMessage->sEmailId = $oFetchResponse->GetFetchValue(FetchType::EMAILID)
|
||||
// ?: $oFetchResponse->GetFetchValue('X-GUID')
|
||||
?: $oFetchResponse->GetFetchValue('X-GM-MSGID');
|
||||
$oMessage->sThreadId = $oFetchResponse->GetFetchValue(FetchType::THREADID)
|
||||
?: $oFetchResponse->GetFetchValue('X-GM-THRID');
|
||||
// https://www.rfc-editor.org/rfc/rfc8474
|
||||
$aEmailId = $oFetchResponse->GetFetchValue(FetchType::EMAILID);
|
||||
$oMessage->sEmailId = $aEmailId ? $aEmailId[0] : $oFetchResponse->GetFetchValue('X-GM-MSGID');
|
||||
// $oMessage->sEmailId = $oMessage->sEmailId ?: $oFetchResponse->GetFetchValue('X-GUID');
|
||||
$aThreadId = $oFetchResponse->GetFetchValue(FetchType::THREADID);
|
||||
$oMessage->sThreadId = $aThreadId ? $aThreadId[0] : $oFetchResponse->GetFetchValue('X-GM-THRID');
|
||||
|
||||
$sCharset = $oBodyStructure ? Utils::NormalizeCharset($oBodyStructure->SearchCharset()) : '';
|
||||
|
||||
$sHeaders = $oFetchResponse->GetHeaderFieldsValue();
|
||||
if (\strlen($sHeaders))
|
||||
{
|
||||
$oHeaders = new \MailSo\Mime\HeaderCollection($sHeaders, false, $sCharset);
|
||||
|
||||
$oHeaders = \strlen($sHeaders) ? new \MailSo\Mime\HeaderCollection($sHeaders, false, $sCharset) : null;
|
||||
if ($oHeaders) {
|
||||
$sContentTypeCharset = $oHeaders->ParameterValue(
|
||||
\MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
|
||||
\MailSo\Mime\Enumerations\Parameter::CHARSET
|
||||
);
|
||||
|
||||
if (\strlen($sContentTypeCharset))
|
||||
{
|
||||
if (\strlen($sContentTypeCharset)) {
|
||||
$sCharset = Utils::NormalizeCharset($sContentTypeCharset);
|
||||
}
|
||||
|
||||
if (\strlen($sCharset))
|
||||
{
|
||||
if (\strlen($sCharset)) {
|
||||
$oHeaders->SetParentCharset($sCharset);
|
||||
}
|
||||
|
||||
|
|
@ -339,48 +183,32 @@ class Message implements \JsonSerializable
|
|||
$oMessage->oCc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::CC, $bCharsetAutoDetect);
|
||||
$oMessage->oBcc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::BCC, $bCharsetAutoDetect);
|
||||
|
||||
if ($oMessage->oFrom) {
|
||||
$oHeaders->PopulateEmailColectionByDkim($oMessage->oFrom);
|
||||
}
|
||||
|
||||
$oMessage->oSender = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::SENDER, $bCharsetAutoDetect);
|
||||
$oMessage->oReplyTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::REPLY_TO, $bCharsetAutoDetect);
|
||||
$oMessage->oDeliveredTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::DELIVERED_TO, $bCharsetAutoDetect);
|
||||
|
||||
$oMessage->sInReplyTo = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IN_REPLY_TO);
|
||||
$oMessage->sReferences = Utils::StripSpaces(
|
||||
$oMessage->InReplyTo = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IN_REPLY_TO);
|
||||
$oMessage->References = Utils::StripSpaces(
|
||||
$oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::REFERENCES));
|
||||
|
||||
$sHeaderDate = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DATE);
|
||||
$oMessage->sHeaderDate = $sHeaderDate;
|
||||
$oMessage->iHeaderTimeStampInUTC = \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($sHeaderDate);
|
||||
$oMessage->iHeaderTimeStampInUTC = \MailSo\Base\DateTimeHelper::ParseRFC2822DateString(
|
||||
$oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DATE)
|
||||
);
|
||||
|
||||
// Priority
|
||||
$oMessage->iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL;
|
||||
$sPriority = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_MSMAIL_PRIORITY);
|
||||
if (!\strlen($sPriority))
|
||||
{
|
||||
$sPriority = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IMPORTANCE);
|
||||
}
|
||||
if (!\strlen($sPriority))
|
||||
{
|
||||
$sPriority = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_PRIORITY);
|
||||
}
|
||||
if (\strlen($sPriority))
|
||||
{
|
||||
switch (\str_replace(' ', '', \strtolower($sPriority)))
|
||||
$sPriority = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_MSMAIL_PRIORITY)
|
||||
?: $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IMPORTANCE)
|
||||
?: $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_PRIORITY);
|
||||
if (\strlen($sPriority)) {
|
||||
switch (\substr(\trim($sPriority), 0, 1))
|
||||
{
|
||||
case 'high':
|
||||
case '1(highest)':
|
||||
case '2(high)':
|
||||
case 'h':
|
||||
case '1':
|
||||
case '2':
|
||||
$oMessage->iPriority = \MailSo\Mime\Enumerations\MessagePriority::HIGH;
|
||||
break;
|
||||
|
||||
case 'low':
|
||||
case '4(low)':
|
||||
case '5(lowest)':
|
||||
case 'l':
|
||||
case '4':
|
||||
case '5':
|
||||
$oMessage->iPriority = \MailSo\Mime\Enumerations\MessagePriority::LOW;
|
||||
|
|
@ -392,26 +220,19 @@ class Message implements \JsonSerializable
|
|||
$oMessage->sDeliveryReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::RETURN_RECEIPT_TO));
|
||||
|
||||
// Read Receipt
|
||||
$oMessage->sReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DISPOSITION_NOTIFICATION_TO));
|
||||
if (empty($oMessage->sReadReceipt))
|
||||
{
|
||||
$oMessage->sReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_CONFIRM_READING_TO));
|
||||
$oMessage->ReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DISPOSITION_NOTIFICATION_TO));
|
||||
if (empty($oMessage->ReadReceipt)) {
|
||||
$oMessage->ReadReceipt = \trim($oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::X_CONFIRM_READING_TO));
|
||||
}
|
||||
|
||||
// Unsubscribe links
|
||||
$oMessage->aUnsubsribeLinks = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::LIST_UNSUBSCRIBE);
|
||||
if (empty($oMessage->aUnsubsribeLinks))
|
||||
{
|
||||
$oMessage->aUnsubsribeLinks = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
$oMessage->aUnsubsribeLinks = explode(',', $oMessage->aUnsubsribeLinks);
|
||||
$oMessage->aUnsubsribeLinks = array_map(
|
||||
$UnsubsribeLinks = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::LIST_UNSUBSCRIBE);
|
||||
if ($UnsubsribeLinks) {
|
||||
$oMessage->UnsubsribeLinks = \array_map(
|
||||
function ($link) {
|
||||
return trim($link, ' <>');
|
||||
},
|
||||
$oMessage->aUnsubsribeLinks
|
||||
\explode(',', $UnsubsribeLinks)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -480,7 +301,22 @@ class Message implements \JsonSerializable
|
|||
}
|
||||
|
||||
if (\strlen($sType) && \strlen($sFolder) && $iUid) {
|
||||
$oMessage->aDraftInfo = array($sType, $iUid, $sFolder);
|
||||
$oMessage->DraftInfo = array($sType, $iUid, $sFolder);
|
||||
}
|
||||
}
|
||||
|
||||
$aAuth = $oHeaders->AuthStatuses();
|
||||
$oMessage->SPF = $aAuth['spf'];
|
||||
$oMessage->DKIM = $aAuth['dkim'];
|
||||
$oMessage->DMARC = $aAuth['dmarc'];
|
||||
if ($aAuth['dkim'] && $oMessage->oFrom) {
|
||||
foreach ($oMessage->oFrom as $oEmail) {
|
||||
$sEmail = $oEmail->GetEmail();
|
||||
foreach ($aAuth['dkim'] as $aDkimData) {
|
||||
if (\strpos($sEmail, $aDkimData[1])) {
|
||||
$oEmail->SetDkimStatus($aDkimData[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -500,16 +336,15 @@ class Message implements \JsonSerializable
|
|||
$oMessage->oTo = $oFetchResponse->GetFetchEnvelopeEmailCollection(5, $sCharset);
|
||||
$oMessage->oCc = $oFetchResponse->GetFetchEnvelopeEmailCollection(6, $sCharset);
|
||||
$oMessage->oBcc = $oFetchResponse->GetFetchEnvelopeEmailCollection(7, $sCharset);
|
||||
$oMessage->sInReplyTo = $oFetchResponse->GetFetchEnvelopeValue(8, '');
|
||||
$oMessage->InReplyTo = $oFetchResponse->GetFetchEnvelopeValue(8, '');
|
||||
}
|
||||
|
||||
if ($oBodyStructure)
|
||||
{
|
||||
if ($oBodyStructure) {
|
||||
$gEncryptedParts = $oBodyStructure->SearchByContentType('multipart/encrypted');
|
||||
foreach ($gEncryptedParts as $oPart) {
|
||||
if ($oPart->IsPgpEncrypted()) {
|
||||
$oMessage->aPgpEncrypted = [
|
||||
'PartId' => $oPart->SubParts()[1]->PartID()
|
||||
$oMessage->pgpEncrypted = [
|
||||
'partId' => $oPart->SubParts()[1]->PartID()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -520,51 +355,47 @@ class Message implements \JsonSerializable
|
|||
continue;
|
||||
}
|
||||
$oPgpSignaturePart = $oPart->SubParts()[1];
|
||||
$oMessage->aPgpSigned = [
|
||||
$oMessage->pgpSigned = [
|
||||
// /?/Raw/&q[]=/0/Download/&q[]=/...
|
||||
// /?/Raw/&q[]=/0/View/&q[]=/...
|
||||
'BodyPartId' => $oPart->SubParts()[0]->PartID(),
|
||||
'SigPartId' => $oPgpSignaturePart->PartID(),
|
||||
'MicAlg' => (string) $oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'micalg')
|
||||
'bodyPartId' => $oPart->SubParts()[0]->PartID(),
|
||||
'sigPartId' => $oPgpSignaturePart->PartID(),
|
||||
'micAlg' => $oHeaders ? (string) $oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'micalg') : ''
|
||||
];
|
||||
/*
|
||||
// An empty section specification refers to the entire message, including the header.
|
||||
// But Dovecot does not return it with BODY.PEEK[1], so we also use BODY.PEEK[1.MIME].
|
||||
$sPgpText = \trim(
|
||||
\trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->aPgpSigned['BodyPartId'].'.MIME]'))
|
||||
\trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->pgpSigned['bodyPartId'].'.MIME]'))
|
||||
. "\r\n\r\n"
|
||||
. \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->aPgpSigned['BodyPartId'].']'))
|
||||
. \trim($oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->pgpSigned['bodyPartId'].']'))
|
||||
);
|
||||
if ($sPgpText) {
|
||||
$oMessage->aPgpSigned['Body'] = $sPgpText;
|
||||
$oMessage->pgpSigned['body'] = $sPgpText;
|
||||
}
|
||||
$sPgpSignatureText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->aPgpSigned['SigPartId'].']');
|
||||
$sPgpSignatureText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oMessage->pgpSigned['sigPartId'].']');
|
||||
if ($sPgpSignatureText && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE')) {
|
||||
$oMessage->aPgpSigned['Signature'] = $oPart->SubParts()[0]->PartID();
|
||||
$oMessage->pgpSigned['signature'] = $oPart->SubParts()[0]->PartID();
|
||||
}
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
$aTextParts = $oBodyStructure->GetHtmlAndPlainParts();
|
||||
if ($aTextParts)
|
||||
{
|
||||
if ($aTextParts) {
|
||||
$sCharset = $sCharset ?: \MailSo\Base\Enumerations\Charset::UTF_8;
|
||||
|
||||
$aHtmlParts = array();
|
||||
$aPlainParts = array();
|
||||
|
||||
foreach ($aTextParts as $oPart)
|
||||
{
|
||||
foreach ($aTextParts as $oPart) {
|
||||
$sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']');
|
||||
if (null === $sText)
|
||||
{
|
||||
if (null === $sText) {
|
||||
// TextPartIsTrimmed ?
|
||||
$sText = $oFetchResponse->GetFetchValue(FetchType::BODY.'['.$oPart->PartID().']<0>');
|
||||
}
|
||||
|
||||
if (\is_string($sText) && \strlen($sText))
|
||||
{
|
||||
if (\is_string($sText) && \strlen($sText)) {
|
||||
$sText = Utils::DecodeEncodingValue($sText, $oPart->MailEncodingName());
|
||||
$sText = Utils::ConvertEncoding($sText,
|
||||
Utils::NormalizeCharset($oPart->Charset() ?: $sCharset, true),
|
||||
|
|
@ -574,34 +405,28 @@ class Message implements \JsonSerializable
|
|||
|
||||
// https://datatracker.ietf.org/doc/html/rfc4880#section-7
|
||||
// Cleartext Signature
|
||||
if (!$oMessage->aPgpSigned && \str_contains($sText, '-----BEGIN PGP SIGNED MESSAGE-----'))
|
||||
{
|
||||
$oMessage->aPgpSigned = [
|
||||
'BodyPartId' => $oPart->PartID()
|
||||
if (!$oMessage->pgpSigned && \str_contains($sText, '-----BEGIN PGP SIGNED MESSAGE-----')) {
|
||||
$oMessage->pgpSigned = [
|
||||
'bodyPartId' => $oPart->PartID()
|
||||
];
|
||||
}
|
||||
|
||||
if (\str_contains($sText, '-----BEGIN PGP MESSAGE-----'))
|
||||
{
|
||||
if (\str_contains($sText, '-----BEGIN PGP MESSAGE-----')) {
|
||||
$keyIds = [];
|
||||
if (\SnappyMail\PGP\GPG::isSupported()) {
|
||||
$GPG = new \SnappyMail\PGP\GPG('');
|
||||
$keyIds = $GPG->getEncryptedMessageKeys($sText);
|
||||
}
|
||||
$oMessage->aPgpEncrypted = [
|
||||
'PartId' => $oPart->PartID(),
|
||||
'KeyIds' => $keyIds
|
||||
$oMessage->pgpEncrypted = [
|
||||
'partId' => $oPart->PartID(),
|
||||
'keyIds' => $keyIds
|
||||
];
|
||||
}
|
||||
|
||||
if ('text/html' === $oPart->ContentType())
|
||||
{
|
||||
if ('text/html' === $oPart->ContentType()) {
|
||||
$aHtmlParts[] = $sText;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($oPart->IsFlowedFormat())
|
||||
{
|
||||
} else {
|
||||
if ($oPart->IsFlowedFormat()) {
|
||||
$sText = Utils::DecodeFlowedFormat($sText);
|
||||
}
|
||||
|
||||
|
|
@ -617,14 +442,12 @@ class Message implements \JsonSerializable
|
|||
}
|
||||
|
||||
$gAttachmentsParts = $oBodyStructure->SearchAttachmentsParts();
|
||||
if ($gAttachmentsParts->valid())
|
||||
{
|
||||
$oMessage->oAttachments = new AttachmentCollection;
|
||||
foreach ($gAttachmentsParts as /* @var $oAttachmentItem \MailSo\Imap\BodyStructure */ $oAttachmentItem)
|
||||
{
|
||||
if ($gAttachmentsParts->valid()) {
|
||||
$oMessage->Attachments = new AttachmentCollection;
|
||||
foreach ($gAttachmentsParts as /* @var $oAttachmentItem \MailSo\Imap\BodyStructure */ $oAttachmentItem) {
|
||||
// if ('application/pgp-keys' === $oAttachmentItem->ContentType()) import ???
|
||||
$oMessage->oAttachments->append(
|
||||
Attachment::NewBodyStructureInstance($oMessage->sFolder, $oMessage->iUid, $oAttachmentItem)
|
||||
$oMessage->Attachments->append(
|
||||
new Attachment($oMessage->sFolder, $oMessage->Uid, $oAttachmentItem)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -633,6 +456,29 @@ class Message implements \JsonSerializable
|
|||
return $oMessage;
|
||||
}
|
||||
|
||||
public function ETag(string $sClientHash) : string
|
||||
{
|
||||
return \md5('MessageHash/' . \implode('/', [
|
||||
$this->sFolder,
|
||||
$this->Uid,
|
||||
\implode(',', $this->getFlags()),
|
||||
// \implode(',', $this->aThreadUIDs),
|
||||
$sClientHash
|
||||
]));
|
||||
}
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc5788#section-3.4.1
|
||||
// Thunderbird $label1 is same as $Important?
|
||||
// Thunderbird $label4 is same as $todo?
|
||||
protected function getFlags() : array
|
||||
{
|
||||
return \array_unique(\str_replace(
|
||||
['$readreceipt', '$replied', /* 'junk', 'nonjunk', '$queued', '$sent', 'sent'*/],
|
||||
['$mdnsent', '\\answered',/* '$junk', '$notjunk', '$submitpending', '$submitted', '$submitted'*/],
|
||||
$this->aFlagsLowerCase
|
||||
));
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
|
|
@ -644,55 +490,105 @@ class Message implements \JsonSerializable
|
|||
$this->aFlagsLowerCase
|
||||
), true);
|
||||
*/
|
||||
// https://datatracker.ietf.org/doc/html/rfc5788#section-3.4.1
|
||||
// Thunderbird $label1 is same as $Important?
|
||||
// Thunderbird $label4 is same as $todo?
|
||||
$aFlags = \array_unique(\str_replace(
|
||||
['$readreceipt', '$replied', /* 'junk', 'nonjunk', '$queued', '$sent', 'sent'*/],
|
||||
['$mdnsent', '\\answered',/* '$junk', '$notjunk', '$submitpending', '$submitted', '$submitted'*/],
|
||||
$this->aFlagsLowerCase
|
||||
));
|
||||
$aAutocrypt = [];
|
||||
if ($this->sAutocrypt) {
|
||||
foreach (\explode(';', $this->sAutocrypt) as $entry) {
|
||||
$entry = \explode('=', \trim($entry), 2);
|
||||
$aAutocrypt[$entry[0]] = $entry[1];
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
$aFlags = $this->getFlags();
|
||||
$sReadReceipt = $this->ReadReceipt;
|
||||
if (\strlen($sReadReceipt) && !\in_array('$forwarded', $aFlags)) {
|
||||
try
|
||||
{
|
||||
if (!\MailSo\Mime\Email::Parse($sReadReceipt)) {
|
||||
$sReadReceipt = '';
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$sReadReceipt = '';
|
||||
}
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'@Object' => 'Object/Message',
|
||||
'Folder' => $this->sFolder,
|
||||
'Uid' => $this->iUid,
|
||||
'folder' => $this->sFolder,
|
||||
'uid' => $this->Uid,
|
||||
'hash' => \md5($this->sFolder . $this->Uid),
|
||||
'subject' => \trim(Utils::Utf8Clear($this->sSubject)),
|
||||
'encrypted' => 'multipart/encrypted' == $this->sContentType || $this->PgpEncrypted(),
|
||||
'MessageId' => $this->sMessageId,
|
||||
'SpamScore' => $this->bIsSpam ? 100 : $this->iSpamScore,
|
||||
'SpamResult' => $this->sSpamResult,
|
||||
'IsSpam' => $this->bIsSpam,
|
||||
'HasVirus' => $this->bHasVirus,
|
||||
'DateTimeStampInUTC' => $this->iInternalTimeStampInUTC,
|
||||
'encrypted' => 'multipart/encrypted' == $this->sContentType || $this->pgpEncrypted,
|
||||
'messageId' => $this->sMessageId,
|
||||
'spamScore' => $this->bIsSpam ? 100 : $this->SpamScore,
|
||||
'spamResult' => $this->sSpamResult,
|
||||
'isSpam' => $this->bIsSpam,
|
||||
'hasVirus' => $this->bHasVirus,
|
||||
// 'virusScanned' => $this->sVirusScanned,
|
||||
'dateTimestamp' => $this->iHeaderTimeStampInUTC ?: $this->iInternalTimeStampInUTC,
|
||||
'internalTimestamp' => $this->iInternalTimeStampInUTC,
|
||||
|
||||
// \MailSo\Mime\EmailCollection
|
||||
'From' => $this->oFrom,
|
||||
'ReplyTo' => $this->oReplyTo,
|
||||
'To' => $this->oTo,
|
||||
'Cc' => $this->oCc,
|
||||
'Bcc' => $this->oBcc,
|
||||
'Sender' => $this->oSender,
|
||||
'DeliveredTo' => $this->oDeliveredTo,
|
||||
'from' => $this->oFrom,
|
||||
'replyTo' => $this->oReplyTo,
|
||||
'to' => $this->oTo,
|
||||
'cc' => $this->oCc,
|
||||
'bcc' => $this->oBcc,
|
||||
'sender' => $this->oSender,
|
||||
'deliveredTo' => $this->oDeliveredTo,
|
||||
|
||||
'Priority' => $this->iPriority,
|
||||
'Threads' => $this->aThreads,
|
||||
'UnsubsribeLinks' => $this->aUnsubsribeLinks,
|
||||
'ReadReceipt' => '',
|
||||
'Autocrypt' => $this->sAutocrypt,
|
||||
'priority' => $this->iPriority,
|
||||
'readReceipt' => $sReadReceipt,
|
||||
'autocrypt' => $aAutocrypt ?: null,
|
||||
|
||||
'Attachments' => $this->oAttachments,
|
||||
'attachments' => $this->Attachments,
|
||||
|
||||
'Flags' => $aFlags,
|
||||
'spf' => $this->SPF,
|
||||
'dkim' => $this->DKIM,
|
||||
'dmarc' => $this->DMARC,
|
||||
|
||||
'flags' => $aFlags,
|
||||
|
||||
'inReplyTo' => $this->InReplyTo,
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc8621#section-4.1.1
|
||||
'id' => $this->sEmailId,
|
||||
// 'blobId' => $this->sEmailIdBlob,
|
||||
'threadId' => $this->sThreadId,
|
||||
// 'threadId' => $this->sThreadId,
|
||||
// 'mailboxIds' => ['mailboxid'=>true],
|
||||
// 'keywords' => $keywords,
|
||||
'size' => $this->iSize,
|
||||
'receivedAt' => \gmdate('Y-m-d\\TH:i:s\\Z', $this->iInternalTimeStampInUTC)
|
||||
'size' => $this->iSize
|
||||
);
|
||||
|
||||
if ($this->DraftInfo) {
|
||||
$result['draftInfo'] = $this->DraftInfo;
|
||||
}
|
||||
if ($this->UnsubsribeLinks) {
|
||||
$result['unsubsribeLinks'] = $this->UnsubsribeLinks;
|
||||
}
|
||||
if ($this->References) {
|
||||
$result['references'] = $this->References;
|
||||
}
|
||||
if ($this->sHtml || $this->sPlain) {
|
||||
$result['html'] = $this->sHtml;
|
||||
$result['plain'] = $this->sPlain;
|
||||
}
|
||||
// $this->GetCapa(Capa::OPEN_PGP) || $this->GetCapa(Capa::GNUPG)
|
||||
if ($this->pgpSigned) {
|
||||
$result['pgpSigned'] = $this->pgpSigned;
|
||||
}
|
||||
if ($this->pgpEncrypted) {
|
||||
$result['pgpEncrypted'] = $this->pgpEncrypted;
|
||||
}
|
||||
|
||||
if ($this->aThreadUIDs) {
|
||||
$result['threads'] = $this->aThreadUIDs;
|
||||
}
|
||||
if ($this->aThreadUnseenUIDs) {
|
||||
$result['threadUnseen'] = $this->aThreadUnseenUIDs;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,52 +18,30 @@ namespace MailSo\Mail;
|
|||
class MessageCollection extends \MailSo\Base\Collection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* Amount of UIDs in this list (could be less then total messages when using threads)
|
||||
*/
|
||||
public $FolderHash = '';
|
||||
public int $totalEmails = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $MessageResultCount = 0;
|
||||
public ?int $totalThreads = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $FolderName = '';
|
||||
public string $FolderName = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $Offset = 0;
|
||||
public int $Offset = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $Limit = 0;
|
||||
public int $Limit = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $Search = '';
|
||||
public string $Search = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $ThreadUid = 0;
|
||||
public int $ThreadUid = 0;
|
||||
|
||||
// MailSo\Imap\FolderInformation
|
||||
public $FolderInfo = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $NewMessages = array();
|
||||
public array $NewMessages = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $Filtered = false;
|
||||
// public bool $Filtered = false;
|
||||
|
||||
public bool $Limited = false;
|
||||
|
||||
public function append($oMessage, bool $bToTop = false) : void
|
||||
{
|
||||
|
|
@ -80,16 +58,16 @@ class MessageCollection extends \MailSo\Base\Collection
|
|||
public function jsonSerialize()
|
||||
{
|
||||
return array_merge(parent::jsonSerialize(), array(
|
||||
'MessageResultCount' => $this->MessageResultCount,
|
||||
'Folder' => $this->FolderName,
|
||||
'FolderHash' => $this->FolderHash,
|
||||
'FolderInfo' => $this->FolderInfo,
|
||||
'ThreadUid' => $this->ThreadUid,
|
||||
'NewMessages' => $this->NewMessages,
|
||||
'Filtered' => $this->Filtered,
|
||||
'Offset' => $this->Offset,
|
||||
'Limit' => $this->Limit,
|
||||
'Search' => $this->Search
|
||||
'totalEmails' => $this->totalEmails,
|
||||
'totalThreads' => $this->totalThreads,
|
||||
'threadUid' => $this->ThreadUid,
|
||||
'newMessages' => $this->NewMessages,
|
||||
// 'filtered' => $this->Filtered,
|
||||
'offset' => $this->Offset,
|
||||
'limit' => $this->Limit,
|
||||
'search' => $this->Search,
|
||||
'limited' => $this->Limited,
|
||||
'folder' => $this->FolderInfo
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,21 +13,30 @@ namespace MailSo\Mail;
|
|||
|
||||
class MessageListParams
|
||||
{
|
||||
public
|
||||
$sFolderName, // string
|
||||
$sSearch = '', // string
|
||||
$oCacher = null, // ?\MailSo\Cache\CacheClient
|
||||
$bUseSortIfSupported = false, // bool
|
||||
$bUseThreads = false, // bool
|
||||
$bHideDeleted = true, // bool
|
||||
$sSort = ''; // string
|
||||
public string
|
||||
$sFolderName,
|
||||
$sSearch = '',
|
||||
$sSort = '';
|
||||
|
||||
protected
|
||||
public ?\MailSo\Cache\CacheClient
|
||||
$oCacher = null;
|
||||
|
||||
public bool
|
||||
$bUseSort = true,
|
||||
$bUseThreads = false,
|
||||
$bHideDeleted = true;
|
||||
|
||||
protected int
|
||||
$iOffset = 0,
|
||||
$iLimit = 10,
|
||||
$iLimit = 0,
|
||||
$iPrevUidNext = 0, // used to check for new messages
|
||||
$iThreadUid = 0;
|
||||
|
||||
/**
|
||||
* Messages with message sequence numbers corresponding to the specified message sequence number set.
|
||||
*/
|
||||
public ?\MailSo\Imap\SequenceSet $oSequenceSet = null;
|
||||
|
||||
public function __get($k)
|
||||
{
|
||||
return \property_exists($this, $k) ? $this->$k : null;
|
||||
|
|
@ -38,7 +47,22 @@ class MessageListParams
|
|||
if ('i' === $k[0]) {
|
||||
$this->$k = \max(0, (int) $v);
|
||||
}
|
||||
// \MailSo\Base\Validator::RangeInt($oParams->iOffset, 0)
|
||||
// \MailSo\Base\Validator::RangeInt($oParams->iLimit, 0, 999)
|
||||
// 0 > $oParams->iOffset
|
||||
// 0 > $oParams->iLimit
|
||||
// 999 < $oParams->iLimit
|
||||
}
|
||||
|
||||
public function hash() : string
|
||||
{
|
||||
return \md5(\implode('-', [
|
||||
$this->sFolderName,
|
||||
$this->iOffset,
|
||||
$this->iLimit,
|
||||
$this->bHideDeleted ? '1' : '0',
|
||||
$this->sSearch,
|
||||
$this->bUseSort ? $this->sSort : '',
|
||||
$this->bUseThreads ? $this->iThreadUid : '',
|
||||
$this->iPrevUidNext
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,55 +22,41 @@ class Attachment
|
|||
*/
|
||||
private $rResource;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sFileName;
|
||||
private string $sFileName;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iFileSize;
|
||||
// private int $iFileSize;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sCID;
|
||||
private string $sContentID;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bIsInline;
|
||||
private bool $bIsInline;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bIsLinked;
|
||||
private bool $bIsLinked;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aCustomContentTypeParams;
|
||||
private array $aCustomContentTypeParams;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sContentLocation;
|
||||
private string $sContentLocation;
|
||||
|
||||
private string $sContentType;
|
||||
|
||||
/**
|
||||
* @param resource $rResource
|
||||
*/
|
||||
function __construct($rResource, string $sFileName, int $iFileSize, bool $bIsInline,
|
||||
bool $bIsLinked, string $sCID, array $aCustomContentTypeParams = [], string $sContentLocation = '')
|
||||
bool $bIsLinked, string $sContentID, array $aCustomContentTypeParams = [],
|
||||
string $sContentLocation = '', string $sContentType = '')
|
||||
{
|
||||
$this->rResource = $rResource;
|
||||
$this->sFileName = $sFileName;
|
||||
$this->iFileSize = $iFileSize;
|
||||
// $this->iFileSize = $iFileSize;
|
||||
$this->bIsInline = $bIsInline;
|
||||
$this->bIsLinked = $bIsLinked;
|
||||
$this->sCID = $sCID;
|
||||
$this->sContentID = $sContentID;
|
||||
$this->aCustomContentTypeParams = $aCustomContentTypeParams;
|
||||
$this->sContentLocation = $sContentLocation;
|
||||
$this->sContentType = $sContentType
|
||||
?: \SnappyMail\File\MimeType::fromStream($rResource, $sFileName)
|
||||
?: \SnappyMail\File\MimeType::fromFilename($sFileName)
|
||||
?: 'application/octet-stream';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -83,7 +69,7 @@ class Attachment
|
|||
|
||||
public function ContentType() : string
|
||||
{
|
||||
return \MailSo\Base\Utils::MimeContentType($this->sFileName);
|
||||
return $this->sContentType;
|
||||
}
|
||||
|
||||
public function CustomContentTypeParams() : array
|
||||
|
|
@ -91,69 +77,33 @@ class Attachment
|
|||
return $this->aCustomContentTypeParams;
|
||||
}
|
||||
|
||||
public function CID() : string
|
||||
{
|
||||
return $this->sCID;
|
||||
}
|
||||
|
||||
public function ContentLocation() : string
|
||||
{
|
||||
return $this->sContentLocation;
|
||||
}
|
||||
|
||||
public function FileName() : string
|
||||
{
|
||||
return $this->sFileName;
|
||||
}
|
||||
|
||||
public function FileSize() : int
|
||||
{
|
||||
return $this->iFileSize;
|
||||
}
|
||||
|
||||
public function IsInline() : bool
|
||||
public function isInline() : bool
|
||||
{
|
||||
return $this->bIsInline;
|
||||
}
|
||||
|
||||
public function IsImage() : bool
|
||||
public function isLinked() : bool
|
||||
{
|
||||
return 'image' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName());
|
||||
}
|
||||
|
||||
public function IsArchive() : bool
|
||||
{
|
||||
return 'archive' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName());
|
||||
}
|
||||
|
||||
public function IsPdf() : bool
|
||||
{
|
||||
return 'pdf' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName());
|
||||
}
|
||||
|
||||
public function IsDoc() : bool
|
||||
{
|
||||
return 'doc' === \MailSo\Base\Utils::ContentTypeType($this->ContentType(), $this->FileName());
|
||||
}
|
||||
|
||||
public function IsLinked() : bool
|
||||
{
|
||||
return $this->bIsLinked && \strlen($this->sCID);
|
||||
return $this->bIsLinked && \strlen($this->sContentID);
|
||||
}
|
||||
|
||||
public function ToPart() : Part
|
||||
{
|
||||
$oAttachmentPart = new Part;
|
||||
|
||||
$sFileName = $this->FileName();
|
||||
$sCID = $this->CID();
|
||||
$sContentLocation = $this->ContentLocation();
|
||||
$sFileName = \trim($this->sFileName);
|
||||
$sContentID = $this->sContentID;
|
||||
$sContentLocation = $this->sContentLocation;
|
||||
|
||||
$oContentTypeParameters = null;
|
||||
$oContentDispositionParameters = null;
|
||||
|
||||
if (\strlen(\trim($sFileName)))
|
||||
{
|
||||
if (\strlen($sFileName)) {
|
||||
$oContentTypeParameters =
|
||||
(new ParameterCollection)->Add(new Parameter(
|
||||
Enumerations\Parameter::NAME, $sFileName));
|
||||
|
|
@ -165,27 +115,25 @@ class Attachment
|
|||
|
||||
$oAttachmentPart->Headers->append(
|
||||
new Header(Enumerations\Header::CONTENT_TYPE,
|
||||
$this->ContentType().';'.
|
||||
(($oContentTypeParameters) ? ' '.$oContentTypeParameters->ToString() : '')
|
||||
$this->ContentType().
|
||||
($oContentTypeParameters ? '; '.$oContentTypeParameters : '')
|
||||
)
|
||||
);
|
||||
|
||||
$oAttachmentPart->Headers->append(
|
||||
new Header(Enumerations\Header::CONTENT_DISPOSITION,
|
||||
($this->IsInline() ? 'inline' : 'attachment').';'.
|
||||
(($oContentDispositionParameters) ? ' '.$oContentDispositionParameters->ToString() : '')
|
||||
($this->isInline() ? 'inline' : 'attachment').
|
||||
($oContentDispositionParameters ? '; '.$oContentDispositionParameters : '')
|
||||
)
|
||||
);
|
||||
|
||||
if (\strlen($sCID))
|
||||
{
|
||||
if (\strlen($sContentID)) {
|
||||
$oAttachmentPart->Headers->append(
|
||||
new Header(Enumerations\Header::CONTENT_ID, $sCID)
|
||||
new Header(Enumerations\Header::CONTENT_ID, $sContentID)
|
||||
);
|
||||
}
|
||||
|
||||
if (\strlen($sContentLocation))
|
||||
{
|
||||
if (\strlen($sContentLocation)) {
|
||||
$oAttachmentPart->Headers->append(
|
||||
new Header(Enumerations\Header::CONTENT_LOCATION, $sContentLocation)
|
||||
);
|
||||
|
|
@ -193,8 +141,7 @@ class Attachment
|
|||
|
||||
$oAttachmentPart->Body = $this->Resource();
|
||||
|
||||
if ('message/rfc822' !== \strtolower($this->ContentType()))
|
||||
{
|
||||
if ('message/rfc822' !== \strtolower($this->ContentType())) {
|
||||
$oAttachmentPart->Headers->append(
|
||||
new Header(
|
||||
Enumerations\Header::CONTENT_TRANSFER_ENCODING,
|
||||
|
|
@ -202,17 +149,13 @@ class Attachment
|
|||
)
|
||||
);
|
||||
|
||||
if (\is_resource($oAttachmentPart->Body))
|
||||
{
|
||||
if (!\MailSo\Base\StreamWrappers\Binary::IsStreamRemembed($oAttachmentPart->Body))
|
||||
{
|
||||
$oAttachmentPart->Body =
|
||||
\MailSo\Base\StreamWrappers\Binary::CreateStream($oAttachmentPart->Body,
|
||||
\MailSo\Base\StreamWrappers\Binary::GetInlineDecodeOrEncodeFunctionName(
|
||||
\MailSo\Base\Enumerations\Encoding::BASE64, false));
|
||||
if (\is_resource($oAttachmentPart->Body) && !\MailSo\Base\StreamWrappers\Binary::IsStreamRemembed($oAttachmentPart->Body)) {
|
||||
$oAttachmentPart->Body =
|
||||
\MailSo\Base\StreamWrappers\Binary::CreateStream($oAttachmentPart->Body,
|
||||
\MailSo\Base\StreamWrappers\Binary::GetInlineDecodeOrEncodeFunctionName(
|
||||
\MailSo\Base\Enumerations\Encoding::BASE64, false));
|
||||
|
||||
\MailSo\Base\StreamWrappers\Binary::RememberStream($oAttachmentPart->Body);
|
||||
}
|
||||
\MailSo\Base\StreamWrappers\Binary::RememberStream($oAttachmentPart->Body);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,33 +17,18 @@ namespace MailSo\Mime;
|
|||
*/
|
||||
class Email implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sDisplayName;
|
||||
private string $sDisplayName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sEmail;
|
||||
private string $sEmail;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sDkimStatus;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sDkimValue;
|
||||
private string $sDkimStatus = Enumerations\DkimStatus::NONE;
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
function __construct(string $sEmail, string $sDisplayName = '')
|
||||
{
|
||||
if (!\strlen(\trim($sEmail)))
|
||||
{
|
||||
if (!\strlen(\trim($sEmail)) && !\strlen(\trim($sDisplayName))) {
|
||||
throw new \InvalidArgumentException;
|
||||
}
|
||||
|
||||
|
|
@ -51,9 +36,6 @@ class Email implements \JsonSerializable
|
|||
\MailSo\Base\Utils::Trim($sEmail), true);
|
||||
|
||||
$this->sDisplayName = \MailSo\Base\Utils::Trim($sDisplayName);
|
||||
|
||||
$this->sDkimStatus = Enumerations\DkimStatus::NONE;
|
||||
$this->sDkimValue = '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -62,8 +44,7 @@ class Email implements \JsonSerializable
|
|||
public static function Parse(string $sEmailAddress) : self
|
||||
{
|
||||
$sEmailAddress = \MailSo\Base\Utils::Trim($sEmailAddress);
|
||||
if (!\strlen(\trim($sEmailAddress)))
|
||||
{
|
||||
if (!\strlen(\trim($sEmailAddress))) {
|
||||
throw new \InvalidArgumentException;
|
||||
}
|
||||
|
||||
|
|
@ -79,20 +60,16 @@ class Email implements \JsonSerializable
|
|||
$iEndIndex = 0;
|
||||
$iCurrentIndex = 0;
|
||||
|
||||
while ($iCurrentIndex < \strlen($sEmailAddress))
|
||||
{
|
||||
while ($iCurrentIndex < \strlen($sEmailAddress)) {
|
||||
switch ($sEmailAddress[$iCurrentIndex])
|
||||
{
|
||||
// case '\'':
|
||||
case '"':
|
||||
// $sQuoteChar = $sEmailAddress[$iCurrentIndex];
|
||||
if ((!$bInName) && (!$bInAddress) && (!$bInComment))
|
||||
{
|
||||
if (!$bInName && !$bInAddress && !$bInComment) {
|
||||
$bInName = true;
|
||||
$iStartIndex = $iCurrentIndex;
|
||||
}
|
||||
else if ((!$bInAddress) && (!$bInComment))
|
||||
{
|
||||
} else if (!$bInAddress && !$bInComment) {
|
||||
$iEndIndex = $iCurrentIndex;
|
||||
$sName = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1);
|
||||
$sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
|
||||
|
|
@ -103,10 +80,8 @@ class Email implements \JsonSerializable
|
|||
}
|
||||
break;
|
||||
case '<':
|
||||
if ((!$bInName) && (!$bInAddress) && (!$bInComment))
|
||||
{
|
||||
if ($iCurrentIndex > 0 && \strlen($sName) === 0)
|
||||
{
|
||||
if (!$bInName && !$bInAddress && !$bInComment) {
|
||||
if ($iCurrentIndex > 0 && !\strlen($sName)) {
|
||||
$sName = \substr($sEmailAddress, 0, $iCurrentIndex);
|
||||
}
|
||||
|
||||
|
|
@ -115,8 +90,7 @@ class Email implements \JsonSerializable
|
|||
}
|
||||
break;
|
||||
case '>':
|
||||
if ($bInAddress)
|
||||
{
|
||||
if ($bInAddress) {
|
||||
$iEndIndex = $iCurrentIndex;
|
||||
$sEmail = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1);
|
||||
$sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
|
||||
|
|
@ -127,15 +101,13 @@ class Email implements \JsonSerializable
|
|||
}
|
||||
break;
|
||||
case '(':
|
||||
if ((!$bInName) && (!$bInAddress) && (!$bInComment))
|
||||
{
|
||||
if (!$bInName && !$bInAddress && !$bInComment) {
|
||||
$bInComment = true;
|
||||
$iStartIndex = $iCurrentIndex;
|
||||
}
|
||||
break;
|
||||
case ')':
|
||||
if ($bInComment)
|
||||
{
|
||||
if ($bInComment) {
|
||||
$iEndIndex = $iCurrentIndex;
|
||||
$sComment = \substr($sEmailAddress, $iStartIndex + 1, $iEndIndex - $iStartIndex - 1);
|
||||
$sEmailAddress = \substr_replace($sEmailAddress, '', $iStartIndex, $iEndIndex - $iStartIndex + 1);
|
||||
|
|
@ -146,28 +118,23 @@ class Email implements \JsonSerializable
|
|||
}
|
||||
break;
|
||||
case '\\':
|
||||
$iCurrentIndex++;
|
||||
++$iCurrentIndex;
|
||||
break;
|
||||
}
|
||||
|
||||
$iCurrentIndex++;
|
||||
++$iCurrentIndex;
|
||||
}
|
||||
|
||||
if (\strlen($sEmail) === 0)
|
||||
{
|
||||
if (!\strlen($sEmail)) {
|
||||
$aRegs = array('');
|
||||
if (\preg_match('/[^@\s]+@\S+/i', $sEmailAddress, $aRegs) && isset($aRegs[0]))
|
||||
{
|
||||
if (\preg_match('/[^@\s]+@\S+/i', $sEmailAddress, $aRegs) && isset($aRegs[0])) {
|
||||
$sEmail = $aRegs[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sName = $sEmailAddress;
|
||||
}
|
||||
}
|
||||
|
||||
if ((\strlen($sEmail) > 0) && (\strlen($sName) == 0) && (\strlen($sComment) == 0))
|
||||
{
|
||||
if (\strlen($sEmail) && !\strlen($sName) && !\strlen($sComment)) {
|
||||
$sName = \str_replace($sEmail, '', $sEmailAddress);
|
||||
}
|
||||
|
||||
|
|
@ -196,16 +163,6 @@ class Email implements \JsonSerializable
|
|||
return $this->sDisplayName;
|
||||
}
|
||||
|
||||
public function GetDkimStatus() : string
|
||||
{
|
||||
return $this->sDkimStatus;
|
||||
}
|
||||
|
||||
public function GetDkimValue() : string
|
||||
{
|
||||
return $this->sDkimValue;
|
||||
}
|
||||
|
||||
public function GetAccountName() : string
|
||||
{
|
||||
return \MailSo\Base\Utils::GetAccountNameFromEmail($this->GetEmail(false));
|
||||
|
|
@ -216,17 +173,9 @@ class Email implements \JsonSerializable
|
|||
return \MailSo\Base\Utils::GetDomainFromEmail($this->GetEmail($bIdn));
|
||||
}
|
||||
|
||||
public function SetDkimStatusAndValue(string $sDkimStatus, string $sDkimValue = '')
|
||||
public function SetDkimStatus(string $sDkimStatus)
|
||||
{
|
||||
$this->sDkimStatus = Enumerations\DkimStatus::normalizeValue($sDkimStatus);
|
||||
$this->sDkimValue = $sDkimValue;
|
||||
}
|
||||
|
||||
public function ToArray(bool $bIdn = false, bool $bDkim = true) : array
|
||||
{
|
||||
return $bDkim ?
|
||||
array($this->sDisplayName, $this->GetEmail($bIdn), $this->sDkimStatus, $this->sDkimValue) :
|
||||
array($this->sDisplayName, $this->GetEmail($bIdn));
|
||||
}
|
||||
|
||||
public function ToString(bool $bConvertSpecialsName = false, bool $bIdn = false) : string
|
||||
|
|
@ -234,19 +183,16 @@ class Email implements \JsonSerializable
|
|||
$sReturn = '';
|
||||
|
||||
$sDisplayName = \str_replace('"', '\"', $this->sDisplayName);
|
||||
if ($bConvertSpecialsName)
|
||||
{
|
||||
$sDisplayName = 0 === \strlen($sDisplayName) ? '' : \MailSo\Base\Utils::EncodeUnencodedValue(
|
||||
\MailSo\Base\Enumerations\Encoding::BASE64_SHORT,
|
||||
$sDisplayName);
|
||||
if ($bConvertSpecialsName) {
|
||||
$sDisplayName = \strlen($sDisplayName)
|
||||
? \MailSo\Base\Utils::EncodeUnencodedValue(\MailSo\Base\Enumerations\Encoding::BASE64_SHORT, $sDisplayName)
|
||||
: '';
|
||||
}
|
||||
|
||||
$sDisplayName = 0 === \strlen($sDisplayName) ? '' : '"'.$sDisplayName.'"';
|
||||
if (\strlen($this->sEmail))
|
||||
{
|
||||
$sDisplayName = \strlen($sDisplayName) ? '"'.$sDisplayName.'"' : '';
|
||||
if (\strlen($this->sEmail)) {
|
||||
$sReturn = $this->GetEmail($bIdn);
|
||||
if (\strlen($sDisplayName))
|
||||
{
|
||||
if (\strlen($sDisplayName)) {
|
||||
$sReturn = $sDisplayName.' <'.$sReturn.'>';
|
||||
}
|
||||
}
|
||||
|
|
@ -254,22 +200,19 @@ class Email implements \JsonSerializable
|
|||
return \trim($sReturn);
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return $this->ToString();
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
/*
|
||||
$BIMI = '';
|
||||
if (Enumerations\DkimStatus::PASS == $this->GetDkimStatus()) {
|
||||
$BIMI = \SnappyMail\DNS\BIMI($this->GetDomain());
|
||||
}
|
||||
*/
|
||||
return array(
|
||||
'@Object' => 'Object/Email',
|
||||
'Name' => \MailSo\Base\Utils::Utf8Clear($this->GetDisplayName()),
|
||||
'Email' => \MailSo\Base\Utils::Utf8Clear($this->GetEmail(true)),
|
||||
'DkimStatus' => $this->GetDkimStatus(),
|
||||
'DkimValue' => $this->GetDkimValue()
|
||||
// 'BIMI' => $BIMI
|
||||
'name' => \MailSo\Base\Utils::Utf8Clear($this->GetDisplayName()),
|
||||
'email' => \MailSo\Base\Utils::Utf8Clear($this->GetEmail(true)),
|
||||
'dkimStatus' => $this->sDkimStatus
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,21 +33,9 @@ class EmailCollection extends \MailSo\Base\Collection
|
|||
parent::append($oEmail, $bToTop);
|
||||
}
|
||||
|
||||
public function ToArray() : array
|
||||
{
|
||||
$aReturn = array();
|
||||
foreach ($this as $oEmail)
|
||||
{
|
||||
$aReturn[] = $oEmail->ToArray();
|
||||
}
|
||||
|
||||
return $aReturn;
|
||||
}
|
||||
|
||||
public function MergeWithOtherCollection(EmailCollection $oEmails) : self
|
||||
{
|
||||
foreach ($oEmails as $oEmail)
|
||||
{
|
||||
foreach ($oEmails as $oEmail) {
|
||||
$this->append($oEmail);
|
||||
}
|
||||
|
||||
|
|
@ -58,11 +46,9 @@ class EmailCollection extends \MailSo\Base\Collection
|
|||
{
|
||||
$aReturn = array();
|
||||
|
||||
foreach ($this as $oEmail)
|
||||
{
|
||||
foreach ($this as $oEmail) {
|
||||
$sEmail = $oEmail->GetEmail();
|
||||
if (!isset($aReturn[$sEmail]))
|
||||
{
|
||||
if (!isset($aReturn[$sEmail])) {
|
||||
$aReturn[$sEmail] = $oEmail;
|
||||
}
|
||||
}
|
||||
|
|
@ -73,22 +59,25 @@ class EmailCollection extends \MailSo\Base\Collection
|
|||
public function ToString(bool $bConvertSpecialsName = false, bool $bIdn = false) : string
|
||||
{
|
||||
$aReturn = array();
|
||||
foreach ($this as $oEmail)
|
||||
{
|
||||
foreach ($this as $oEmail) {
|
||||
$aReturn[] = $oEmail->ToString($bConvertSpecialsName, $bIdn);
|
||||
}
|
||||
|
||||
return \implode(', ', $aReturn);
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return $this->ToString();
|
||||
}
|
||||
|
||||
private function parseEmailAddresses(string $sRawEmails) : void
|
||||
{
|
||||
// $sRawEmails = \MailSo\Base\Utils::Trim($sRawEmails);
|
||||
$sRawEmails = \trim($sRawEmails);
|
||||
|
||||
$sWorkingRecipientsLen = \strlen($sRawEmails);
|
||||
if (!$sWorkingRecipientsLen)
|
||||
{
|
||||
if (!$sWorkingRecipientsLen) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -102,59 +91,49 @@ class EmailCollection extends \MailSo\Base\Collection
|
|||
|
||||
$iCurrentPos = 0;
|
||||
|
||||
while ($iCurrentPos < $sWorkingRecipientsLen)
|
||||
{
|
||||
while ($iCurrentPos < $sWorkingRecipientsLen) {
|
||||
switch ($sRawEmails[$iCurrentPos])
|
||||
{
|
||||
case '\'':
|
||||
case '"':
|
||||
if (!$bIsInQuotes)
|
||||
{
|
||||
if (!$bIsInQuotes) {
|
||||
$sChQuote = $sRawEmails[$iCurrentPos];
|
||||
$bIsInQuotes = true;
|
||||
}
|
||||
else if ($sChQuote == $sRawEmails[$iCurrentPos])
|
||||
{
|
||||
} else if ($sChQuote == $sRawEmails[$iCurrentPos]) {
|
||||
$bIsInQuotes = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case '<':
|
||||
if (!$bIsInAngleBrackets)
|
||||
{
|
||||
if (!$bIsInAngleBrackets) {
|
||||
$bIsInAngleBrackets = true;
|
||||
if ($bIsInQuotes)
|
||||
{
|
||||
if ($bIsInQuotes) {
|
||||
$bIsInQuotes = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '>':
|
||||
if ($bIsInAngleBrackets)
|
||||
{
|
||||
if ($bIsInAngleBrackets) {
|
||||
$bIsInAngleBrackets = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case '(':
|
||||
if (!$bIsInBrackets)
|
||||
{
|
||||
if (!$bIsInBrackets) {
|
||||
$bIsInBrackets = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case ')':
|
||||
if ($bIsInBrackets)
|
||||
{
|
||||
if ($bIsInBrackets) {
|
||||
$bIsInBrackets = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case ',':
|
||||
case ';':
|
||||
if (!$bIsInAngleBrackets && !$bIsInBrackets && !$bIsInQuotes)
|
||||
{
|
||||
if (!$bIsInAngleBrackets && !$bIsInBrackets && !$bIsInQuotes) {
|
||||
$iEmailEndPos = $iCurrentPos;
|
||||
|
||||
try
|
||||
|
|
@ -175,8 +154,7 @@ class EmailCollection extends \MailSo\Base\Collection
|
|||
++$iCurrentPos;
|
||||
}
|
||||
|
||||
if ($iEmailStartPos < $iCurrentPos)
|
||||
{
|
||||
if ($iEmailStartPos < $iCurrentPos) {
|
||||
try
|
||||
{
|
||||
$this->append(
|
||||
|
|
@ -186,4 +164,11 @@ class EmailCollection extends \MailSo\Base\Collection
|
|||
catch (\InvalidArgumentException $oException) {}
|
||||
}
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return \array_slice($this->getArrayCopy(), 0, 100);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ abstract class Header
|
|||
CONTENT_DISPOSITION = 'Content-Disposition',
|
||||
CONTENT_DESCRIPTION = 'Content-Description',
|
||||
CONTENT_ID = 'Content-ID',
|
||||
// CONTENT_BASE = 'Content-Base', // rfc2110
|
||||
CONTENT_LOCATION = 'Content-Location',
|
||||
|
||||
RECEIVED_SPF = 'Received-SPF',
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2014 Usenko Timur
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo\Mime\Enumerations;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Mime
|
||||
* @subpackage Enumerations
|
||||
*/
|
||||
abstract class MimeType
|
||||
{
|
||||
const TEXT_PLAIN = 'text/plain';
|
||||
|
||||
const MULTIPART_ALTERNATIVE = 'multipart/alternative';
|
||||
const MULTIPART_RELATED = 'multipart/related';
|
||||
const MULTIPART_MIXED = 'multipart/mixed';
|
||||
|
||||
const MULTIPART_SIGNED = 'multipart/signed';
|
||||
const APPLICATION_PGP_SIGNATURE = 'application/pgp-signature';
|
||||
|
||||
const MULTIPART_ENCRYPTED = 'multipart/encrypted';
|
||||
const APPLICATION_PGP_ENCRYPTED = 'application/pgp-encrypted';
|
||||
const APPLICATION_OCTET_STREAM = 'application/octet-stream';
|
||||
|
||||
const MESSAGE_RFC822 = 'message/rfc822';
|
||||
const MESSAGE_PARTIAL = 'message/partial';
|
||||
const MESSAGE_REPORT = 'message/report';
|
||||
|
||||
const APPLICATION_MS_TNEF = 'application/ms-tnef';
|
||||
const APPLICATION_PKCS7_MIME = 'application/pkcs7-mime';
|
||||
const APPLICATION_PKCS7_SIGNATURE = 'application/pkcs7-signature';
|
||||
}
|
||||
|
|
@ -17,35 +17,17 @@ namespace MailSo\Mime;
|
|||
*/
|
||||
class Header
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sName;
|
||||
private string $sName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sValue;
|
||||
private string $sValue;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sFullValue;
|
||||
private string $sFullValue;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sEncodedValueForReparse;
|
||||
private string $sEncodedValueForReparse;
|
||||
|
||||
/**
|
||||
* @var ParameterCollection
|
||||
*/
|
||||
private $oParameters;
|
||||
private ?ParameterCollection $oParameters = null;
|
||||
|
||||
/**
|
||||
* @var strign
|
||||
*/
|
||||
private $sParentCharset;
|
||||
private string $sParentCharset;
|
||||
|
||||
function __construct(string $sName, string $sValue = '', string $sEncodedValueForReparse = '', string $sParentCharset = '')
|
||||
{
|
||||
|
|
@ -59,26 +41,19 @@ class Header
|
|||
$this->sFullValue = \trim($sValue);
|
||||
$this->sEncodedValueForReparse = '';
|
||||
|
||||
if (\strlen($sEncodedValueForReparse) && $this->IsReparsed())
|
||||
{
|
||||
if (\strlen($sEncodedValueForReparse) && $this->IsReparsed()) {
|
||||
$this->sEncodedValueForReparse = \trim($sEncodedValueForReparse);
|
||||
}
|
||||
|
||||
if (\strlen($this->sFullValue) && $this->IsParameterized())
|
||||
{
|
||||
if (\strlen($this->sFullValue) && $this->IsParameterized()) {
|
||||
$aRawExplode = \explode(';', $this->sFullValue, 2);
|
||||
if (2 === \count($aRawExplode))
|
||||
{
|
||||
if (2 === \count($aRawExplode)) {
|
||||
$this->sValue = $aRawExplode[0];
|
||||
$this->oParameters = new ParameterCollection($aRawExplode[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$this->sValue = $this->sFullValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$this->sValue = $this->sFullValue;
|
||||
}
|
||||
|
||||
|
|
@ -89,14 +64,12 @@ class Header
|
|||
|
||||
public static function NewInstanceFromEncodedString(string $sEncodedLines, string $sIncomingCharset = \MailSo\Base\Enumerations\Charset::ISO_8859_1) : Header
|
||||
{
|
||||
if (empty($sIncomingCharset))
|
||||
{
|
||||
if (empty($sIncomingCharset)) {
|
||||
$sIncomingCharset = \MailSo\Base\Enumerations\Charset::ISO_8859_1;
|
||||
}
|
||||
|
||||
$aParts = \explode(':', \str_replace("\r", '', $sEncodedLines), 2);
|
||||
if (isset($aParts[0]) && isset($aParts[1]) && \strlen($aParts[0]) && \strlen($aParts[1]))
|
||||
{
|
||||
if (isset($aParts[0]) && isset($aParts[1]) && \strlen($aParts[0]) && \strlen($aParts[1])) {
|
||||
return new self(
|
||||
\trim($aParts[0]),
|
||||
\trim(\MailSo\Base\Utils::DecodeHeaderValue(\trim($aParts[1]), $sIncomingCharset)),
|
||||
|
|
@ -130,8 +103,7 @@ class Header
|
|||
|
||||
public function SetParentCharset(string $sParentCharset) : Header
|
||||
{
|
||||
if ($this->sParentCharset !== $sParentCharset && $this->IsReparsed() && \strlen($this->sEncodedValueForReparse))
|
||||
{
|
||||
if ($this->sParentCharset !== $sParentCharset && $this->IsReparsed() && \strlen($this->sEncodedValueForReparse)) {
|
||||
$this->initInputData(
|
||||
$this->sName,
|
||||
\trim(\MailSo\Base\Utils::DecodeHeaderValue($this->sEncodedValueForReparse, $sParentCharset)),
|
||||
|
|
@ -154,22 +126,12 @@ class Header
|
|||
$this->oParameters->setParameter($sName, $sValue);
|
||||
}
|
||||
|
||||
private function wordWrapHelper(string $sValue, string $sGlue = "\r\n ") : string
|
||||
{
|
||||
return \trim(\substr(\wordwrap($this->NameWithDelimitrom().$sValue,
|
||||
74, $sGlue
|
||||
), \strlen($this->NameWithDelimitrom())));
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
$sResult = $this->sFullValue;
|
||||
|
||||
if ($this->IsSubject())
|
||||
{
|
||||
if (!\MailSo\Base\Utils::IsAscii($sResult) &&
|
||||
\function_exists('iconv_mime_encode'))
|
||||
{
|
||||
if ($this->IsSubject()) {
|
||||
if (!\MailSo\Base\Utils::IsAscii($sResult) && \function_exists('iconv_mime_encode')) {
|
||||
$aPreferences = array(
|
||||
// 'scheme' => \MailSo\Base\Enumerations\Encoding::QUOTED_PRINTABLE_SHORT,
|
||||
'scheme' => \MailSo\Base\Enumerations\Encoding::BASE64_SHORT,
|
||||
|
|
@ -182,20 +144,20 @@ class Header
|
|||
return \iconv_mime_encode($this->Name(), $sResult, $aPreferences);
|
||||
}
|
||||
}
|
||||
else if ($this->IsParameterized() && 0 < $this->oParameters->Count())
|
||||
else if ($this->IsParameterized() && 0 < $this->oParameters->count())
|
||||
{
|
||||
$sResult = $this->sValue.'; '.$this->oParameters->ToString(true);
|
||||
}
|
||||
else if ($this->IsEmail())
|
||||
{
|
||||
$oEmailCollection = new EmailCollection($this->sFullValue);
|
||||
if ($oEmailCollection && 0 < $oEmailCollection->Count())
|
||||
{
|
||||
$sResult = $oEmailCollection->ToString(true, false);
|
||||
if ($oEmailCollection && $oEmailCollection->count()) {
|
||||
$sResult = $oEmailCollection->ToString(true);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->NameWithDelimitrom().$this->wordWrapHelper($sResult);
|
||||
// https://www.rfc-editor.org/rfc/rfc2822#section-2.1.1
|
||||
return \wordwrap($this->NameWithDelimitrom() . $sResult, 78, "\r\n ");
|
||||
}
|
||||
|
||||
public function IsSubject() : bool
|
||||
|
|
|
|||
|
|
@ -18,12 +18,9 @@ namespace MailSo\Mime;
|
|||
class HeaderCollection extends \MailSo\Base\Collection
|
||||
{
|
||||
|
||||
protected $sRawHeaders = '';
|
||||
protected string $sRawHeaders = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sParentCharset = '';
|
||||
protected string $sParentCharset = '';
|
||||
|
||||
function __construct(string $sRawHeaders = '', bool $bStoreRawHeaders = true, string $sParentCharset = '')
|
||||
{
|
||||
|
|
@ -79,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 && 0 < $oResult->Count() ? $oResult : null;
|
||||
return new EmailCollection(
|
||||
$this->ValueByName($sHeaderName, $bCharsetAutoDetect)
|
||||
);
|
||||
}
|
||||
|
||||
public function ParametersByName(string $sHeaderName) : ?ParameterCollection
|
||||
|
|
@ -149,40 +143,29 @@ class HeaderCollection extends \MailSo\Base\Collection
|
|||
}
|
||||
|
||||
$sFirstChar = \substr($sHeadersValue, 0, 1);
|
||||
if ($sFirstChar !== ' ' && $sFirstChar !== "\t" && false === \strpos($sHeadersValue, ':'))
|
||||
{
|
||||
if ($sFirstChar !== ' ' && $sFirstChar !== "\t" && false === \strpos($sHeadersValue, ':')) {
|
||||
continue;
|
||||
}
|
||||
else if (null !== $sName && ($sFirstChar === ' ' || $sFirstChar === "\t"))
|
||||
{
|
||||
if (null !== $sName && ($sFirstChar === ' ' || $sFirstChar === "\t")) {
|
||||
$sValue = \is_null($sValue) ? '' : $sValue;
|
||||
|
||||
if ('?=' === \substr(\rtrim($sHeadersValue), -2))
|
||||
{
|
||||
if ('?=' === \substr(\rtrim($sHeadersValue), -2)) {
|
||||
$sHeadersValue = \rtrim($sHeadersValue);
|
||||
}
|
||||
|
||||
if ('=?' === \substr(\ltrim($sHeadersValue), 0, 2))
|
||||
{
|
||||
if ('=?' === \substr(\ltrim($sHeadersValue), 0, 2)) {
|
||||
$sHeadersValue = \ltrim($sHeadersValue);
|
||||
}
|
||||
|
||||
if ('=?' === \substr($sHeadersValue, 0, 2))
|
||||
{
|
||||
if ('=?' === \substr($sHeadersValue, 0, 2)) {
|
||||
$sValue .= $sHeadersValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sValue .= "\n".$sHeadersValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (null !== $sName)
|
||||
{
|
||||
} else {
|
||||
if (null !== $sName) {
|
||||
$oHeader = Header::NewInstanceFromEncodedString($sName.': '.$sValue, $this->sParentCharset);
|
||||
if ($oHeader)
|
||||
{
|
||||
if ($oHeader) {
|
||||
$this->append($oHeader);
|
||||
}
|
||||
|
||||
|
|
@ -194,18 +177,15 @@ class HeaderCollection extends \MailSo\Base\Collection
|
|||
$sName = $aHeaderParts[0];
|
||||
$sValue = isset($aHeaderParts[1]) ? $aHeaderParts[1] : '';
|
||||
|
||||
if ('?=' === \substr(\rtrim($sValue), -2))
|
||||
{
|
||||
if ('?=' === \substr(\rtrim($sValue), -2)) {
|
||||
$sValue = \rtrim($sValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $sName)
|
||||
{
|
||||
if (null !== $sName) {
|
||||
$oHeader = Header::NewInstanceFromEncodedString($sName.': '.$sValue, $this->sParentCharset);
|
||||
if ($oHeader)
|
||||
{
|
||||
if ($oHeader) {
|
||||
$this->append($oHeader);
|
||||
}
|
||||
}
|
||||
|
|
@ -213,72 +193,46 @@ class HeaderCollection extends \MailSo\Base\Collection
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function DkimStatuses() : array
|
||||
/**
|
||||
* https://www.rfc-editor.org/rfc/rfc8601
|
||||
* dkim=pass header.d=domain.tld header.s=s1 header.b=F2SfoZWw;
|
||||
* spf=pass (ORIGINATING: domain of "snappymail@domain.tld" designates 0.0.0.0 as permitted sender) smtp.mailfrom="snappymail@domain.tld";
|
||||
* dmarc=fail reason="SPF not aligned (relaxed), DKIM not aligned (relaxed)" header.from=domain.tld (policy=none)
|
||||
*/
|
||||
public function AuthStatuses() : array
|
||||
{
|
||||
$aResult = array();
|
||||
|
||||
$aResult = [
|
||||
'dkim' => [],
|
||||
'dmarc' => [],
|
||||
'spf' => []
|
||||
];
|
||||
$aHeaders = $this->ValuesByName(Enumerations\Header::AUTHENTICATION_RESULTS);
|
||||
if (\count($aHeaders))
|
||||
{
|
||||
foreach ($aHeaders as $sHeaderValue)
|
||||
{
|
||||
$sStatus = '';
|
||||
$sHeader = '';
|
||||
$sDkimLine = '';
|
||||
|
||||
$aMatch = array();
|
||||
|
||||
$sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue);
|
||||
|
||||
if (\preg_match('/dkim=.+/i', $sHeaderValue, $aMatch) && !empty($aMatch[0]))
|
||||
{
|
||||
$sDkimLine = $aMatch[0];
|
||||
|
||||
$aMatch = array();
|
||||
if (\preg_match('/dkim=([a-zA-Z0-9]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[1]))
|
||||
{
|
||||
$sStatus = $aMatch[1];
|
||||
}
|
||||
|
||||
$aMatch = array();
|
||||
if (\preg_match('/header\.(d|i|from)=([^\s;]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[2]))
|
||||
{
|
||||
$sHeader = \trim($aMatch[2]);
|
||||
}
|
||||
|
||||
if (!empty($sStatus) && !empty($sHeader))
|
||||
{
|
||||
$aResult[] = array($sStatus, $sHeader, $sDkimLine);
|
||||
}
|
||||
if (\count($aHeaders)) {
|
||||
$aHeaders = \implode(';', $aHeaders);
|
||||
$aHeaders = \preg_replace('/[\\r\\n\\t\\s]+/', ' ', $aHeaders);
|
||||
$aHeaders = \explode(';', $aHeaders);
|
||||
foreach ($aHeaders as $sLine) {
|
||||
$aStatus = array();
|
||||
$aHeader = array();
|
||||
if (\preg_match("/(dkim|dmarc|spf)=([a-z0-9]+).*?(;|$)/Di", $sLine, $aStatus)
|
||||
&& \preg_match('/(?:header\\.(?:d|i|from)|smtp.mailfrom)="?([^\\s;"]+)/i', $sLine, $aHeader)
|
||||
) {
|
||||
$sType = \strtolower($aStatus[1]);
|
||||
$aResult[$sType][] = array(\strtolower($aStatus[2]), $aHeader[1], \trim($sLine));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!\count($aResult['dkim'])) {
|
||||
// X-DKIM-Authentication-Results: signer="hostinger.com" status="pass"
|
||||
$aHeaders = $this->ValuesByName(Enumerations\Header::X_DKIM_AUTHENTICATION_RESULTS);
|
||||
foreach ($aHeaders as $sHeaderValue)
|
||||
{
|
||||
$sStatus = '';
|
||||
$sHeader = '';
|
||||
|
||||
$aMatch = array();
|
||||
|
||||
$sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue);
|
||||
|
||||
if (\preg_match('/status[\s]?=[\s]?"([a-zA-Z0-9]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1]))
|
||||
{
|
||||
$sStatus = $aMatch[1];
|
||||
}
|
||||
|
||||
if (\preg_match('/signer[\s]?=[\s]?"([^";]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1]))
|
||||
{
|
||||
$sHeader = \trim($aMatch[1]);
|
||||
}
|
||||
|
||||
if (!empty($sStatus) && !empty($sHeader))
|
||||
{
|
||||
$aResult[] = array($sStatus, $sHeader, $sHeaderValue);
|
||||
foreach ($aHeaders as $sHeaderValue) {
|
||||
$aStatus = array();
|
||||
$aHeader = array();
|
||||
$sHeaderValue = \preg_replace('/[\\r\\n\\t\\s]+/', ' ', $sHeaderValue);
|
||||
if (\preg_match('/status[\\s]?=[\\s]?"([a-zA-Z0-9]+)"/i', $sHeaderValue, $aStatus) && !empty($aStatus[1])
|
||||
&& \preg_match('/signer[\\s]?=[\\s]?"([^";]+)"/i', $sHeaderValue, $aHeader) && !empty($aHeader[1])
|
||||
) {
|
||||
$aResult['dkim'][] = array($aStatus[1], \trim($aHeader[1]), $sHeaderValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -286,21 +240,6 @@ class HeaderCollection extends \MailSo\Base\Collection
|
|||
return $aResult;
|
||||
}
|
||||
|
||||
public function PopulateEmailColectionByDkim(EmailCollection $oEmails) : void
|
||||
{
|
||||
$aDkimStatuses = $this->DkimStatuses();
|
||||
foreach ($oEmails as $oEmail) {
|
||||
$sEmail = $oEmail->GetEmail();
|
||||
foreach ($aDkimStatuses as $aDkimData) {
|
||||
if (isset($aDkimData[0], $aDkimData[1]) &&
|
||||
$aDkimData[1] === \strstr($sEmail, $aDkimData[1]))
|
||||
{
|
||||
$oEmail->SetDkimStatusAndValue($aDkimData[0], empty($aDkimData[2]) ? '' : $aDkimData[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return \implode("\r\n", $this->getArrayCopy());
|
||||
|
|
|
|||
|
|
@ -17,10 +17,7 @@ namespace MailSo\Mime;
|
|||
*/
|
||||
class Message extends Part
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aHeadersValue = array(
|
||||
private array $aHeadersValue = array(
|
||||
/*
|
||||
Enumerations\Header::BCC => '',
|
||||
Enumerations\Header::CC => '',
|
||||
|
|
@ -42,22 +39,11 @@ class Message extends Part
|
|||
*/
|
||||
);
|
||||
|
||||
/**
|
||||
* @var AttachmentCollection
|
||||
*/
|
||||
private $oAttachmentCollection;
|
||||
private AttachmentCollection $oAttachmentCollection;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bAddEmptyTextPart = true;
|
||||
private bool $bAddEmptyTextPart = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bAddDefaultXMailer = true;
|
||||
|
||||
public $messageIdRequired = true;
|
||||
private bool $bAddDefaultXMailer = true;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
|
|
@ -177,8 +163,7 @@ class Message extends Part
|
|||
public function SetCustomHeader(string $sHeaderName, string $sValue) : self
|
||||
{
|
||||
$sHeaderName = \trim($sHeaderName);
|
||||
if (\strlen($sHeaderName))
|
||||
{
|
||||
if (\strlen($sHeaderName)) {
|
||||
$this->aHeadersValue[$sHeaderName] = $sValue;
|
||||
}
|
||||
|
||||
|
|
@ -194,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;
|
||||
}
|
||||
|
||||
|
|
@ -215,11 +203,6 @@ class Message extends Part
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function SetReadConfirmation(string $sEmail) : self
|
||||
{
|
||||
return $this->SetReadReceipt($sEmail);
|
||||
}
|
||||
|
||||
public function SetPriority(int $iValue) : self
|
||||
{
|
||||
$sResult = '';
|
||||
|
|
@ -236,8 +219,7 @@ class Message extends Part
|
|||
break;
|
||||
}
|
||||
|
||||
if (\strlen($sResult))
|
||||
{
|
||||
if (\strlen($sResult)) {
|
||||
$this->aHeadersValue[Enumerations\Header::X_PRIORITY] = $sResult;
|
||||
}
|
||||
|
||||
|
|
@ -260,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;
|
||||
}
|
||||
|
||||
|
|
@ -274,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;
|
||||
}
|
||||
|
||||
|
|
@ -313,18 +299,15 @@ class Message extends Part
|
|||
|
||||
private function generateNewMessageId(string $sHostName = '') : string
|
||||
{
|
||||
if (0 === \strlen($sHostName))
|
||||
{
|
||||
if (!\strlen($sHostName)) {
|
||||
$sHostName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '';
|
||||
}
|
||||
|
||||
if (empty($sHostName) && \MailSo\Base\Utils::FunctionCallable('php_uname'))
|
||||
{
|
||||
if (empty($sHostName) && \MailSo\Base\Utils::FunctionCallable('php_uname')) {
|
||||
$sHostName = \php_uname('n');
|
||||
}
|
||||
|
||||
if (empty($sHostName))
|
||||
{
|
||||
if (empty($sHostName)) {
|
||||
$sHostName = 'localhost';
|
||||
}
|
||||
|
||||
|
|
@ -361,7 +344,7 @@ class Message extends Part
|
|||
}
|
||||
$oPart->Headers->append(
|
||||
new Header(Enumerations\Header::CONTENT_TYPE,
|
||||
$aAttachments[0]->ContentType().'; '.$oParameters->ToString())
|
||||
$aAttachments[0]->ContentType().'; '.$oParameters)
|
||||
);
|
||||
|
||||
if ($resource = $aAttachments[0]->Resource()) {
|
||||
|
|
@ -384,13 +367,10 @@ class Message extends Part
|
|||
if (1 == \count($this->SubParts)) {
|
||||
$oRootPart = $this->SubParts[0];
|
||||
foreach ($this->oAttachmentCollection as $oAttachment) {
|
||||
if ($oAttachment->IsLinked()) {
|
||||
if ($oAttachment->isLinked()) {
|
||||
$oRelatedPart = new Part;
|
||||
$oRelatedPart->Headers->append(
|
||||
new Header(
|
||||
Enumerations\Header::CONTENT_TYPE,
|
||||
Enumerations\MimeType::MULTIPART_RELATED
|
||||
)
|
||||
new Header(Enumerations\Header::CONTENT_TYPE, 'multipart/related')
|
||||
);
|
||||
$oRelatedPart->SubParts->append($oRootPart);
|
||||
$oRootPart = $oRelatedPart;
|
||||
|
|
@ -399,24 +379,18 @@ class Message extends Part
|
|||
}
|
||||
} else {
|
||||
$oRootPart = new Part;
|
||||
$oRootPart->Headers->AddByName(
|
||||
Enumerations\Header::CONTENT_TYPE,
|
||||
Enumerations\MimeType::MULTIPART_MIXED
|
||||
);
|
||||
$oRootPart->Headers->AddByName(Enumerations\Header::CONTENT_TYPE, 'multipart/mixed');
|
||||
$oRootPart->SubParts = $this->SubParts;
|
||||
}
|
||||
|
||||
$oMixedPart = null;
|
||||
foreach ($this->oAttachmentCollection as $oAttachment) {
|
||||
if ($oRelatedPart && $oAttachment->IsLinked()) {
|
||||
if ($oRelatedPart && $oAttachment->isLinked()) {
|
||||
$oRelatedPart->SubParts->append($oAttachment->ToPart());
|
||||
} else {
|
||||
if (!$oMixedPart) {
|
||||
$oMixedPart = new Part;
|
||||
$oMixedPart->Headers->AddByName(
|
||||
Enumerations\Header::CONTENT_TYPE,
|
||||
Enumerations\MimeType::MULTIPART_MIXED
|
||||
);
|
||||
$oMixedPart->Headers->AddByName(Enumerations\Header::CONTENT_TYPE, 'multipart/mixed');
|
||||
$oMixedPart->SubParts->append($oRootPart);
|
||||
$oRootPart = $oMixedPart;
|
||||
}
|
||||
|
|
@ -441,7 +415,7 @@ class Message extends Part
|
|||
$oRootPart->Headers->SetByName(Enumerations\Header::DATE, \gmdate('r'), true);
|
||||
}
|
||||
|
||||
if ($this->messageIdRequired && !isset($this->aHeadersValue[Enumerations\Header::MESSAGE_ID])) {
|
||||
if (!isset($this->aHeadersValue[Enumerations\Header::MESSAGE_ID])) {
|
||||
$oRootPart->Headers->SetByName(Enumerations\Header::MESSAGE_ID, $this->generateNewMessageId(), true);
|
||||
}
|
||||
|
||||
|
|
@ -455,11 +429,6 @@ class Message extends Part
|
|||
|
||||
foreach ($this->aHeadersValue as $sName => $mValue) {
|
||||
if (!($bWithoutBcc && \strtolower(Enumerations\Header::BCC) === \strtolower($sName))) {
|
||||
if (\is_object($mValue)) {
|
||||
if ($mValue instanceof EmailCollection || $mValue instanceof Email || $mValue instanceof ParameterCollection) {
|
||||
$mValue = $mValue->ToString();
|
||||
}
|
||||
}
|
||||
$oRootPart->Headers->SetByName($sName, (string) $mValue);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,17 +15,11 @@ namespace MailSo\Mime;
|
|||
* @category MailSo
|
||||
* @package Mime
|
||||
*/
|
||||
class Parameter
|
||||
class Parameter implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sName;
|
||||
private string $sName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sValue;
|
||||
private string $sValue;
|
||||
|
||||
function __construct(string $sName, string $sValue)
|
||||
{
|
||||
|
|
@ -66,12 +60,11 @@ class Parameter
|
|||
{
|
||||
$this->Reset();
|
||||
|
||||
$aParts = explode($sSeparator, $sRawParam, 2);
|
||||
$aParts = \explode($sSeparator, $sRawParam, 2);
|
||||
|
||||
$this->sName = trim(trim($aParts[0]), '"\'');
|
||||
if (2 === count($aParts))
|
||||
{
|
||||
$this->sValue = trim(trim($aParts[1]), '"\'');
|
||||
$this->sName = \trim(\trim($aParts[0]), '"\'');
|
||||
if (2 === \count($aParts)) {
|
||||
$this->sValue = \trim(\trim($aParts[1]), '"\'');
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
@ -79,25 +72,34 @@ class Parameter
|
|||
|
||||
public function ToString(bool $bConvertSpecialsName = false) : string
|
||||
{
|
||||
$sResult = '';
|
||||
if (\strlen($this->sName))
|
||||
{
|
||||
$sResult = $this->sName.'=';
|
||||
if ($bConvertSpecialsName && in_array(strtolower($this->sName), array(
|
||||
strtolower(Enumerations\Parameter::NAME),
|
||||
strtolower(Enumerations\Parameter::FILENAME)
|
||||
)))
|
||||
{
|
||||
$sResult .= '"'.\MailSo\Base\Utils::EncodeUnencodedValue(
|
||||
\MailSo\Base\Enumerations\Encoding::BASE64_SHORT,
|
||||
$this->sValue).'"';
|
||||
}
|
||||
else
|
||||
{
|
||||
$sResult .= '"'.$this->sValue.'"';
|
||||
}
|
||||
if (!\strlen($this->sName)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
if ($bConvertSpecialsName && \in_array(\strtolower($this->sName), array(
|
||||
\strtolower(Enumerations\Parameter::NAME),
|
||||
\strtolower(Enumerations\Parameter::FILENAME)
|
||||
)))
|
||||
{
|
||||
return $this->sName . '="' . \MailSo\Base\Utils::EncodeUnencodedValue(
|
||||
\MailSo\Base\Enumerations\Encoding::BASE64_SHORT,
|
||||
$this->sValue) . '"';
|
||||
}
|
||||
|
||||
return $this->sName . '="' . $this->sValue . '"';
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return $this->ToString();
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return array(
|
||||
'name' => $this->sName,
|
||||
'value' => $this->sValue
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ class ParameterCollection extends \MailSo\Base\Collection
|
|||
{
|
||||
parent::__construct();
|
||||
|
||||
if (\strlen($sRawParams))
|
||||
{
|
||||
$this->Parse($sRawParams);
|
||||
}
|
||||
\strlen($sRawParams) && $this->Parse($sRawParams);
|
||||
}
|
||||
|
||||
public function append($oParameter, bool $bToTop = false) : void
|
||||
|
|
@ -66,8 +63,7 @@ class ParameterCollection extends \MailSo\Base\Collection
|
|||
|
||||
$aDataToParse = \explode(';', $sRawParams);
|
||||
|
||||
foreach ($aDataToParse as $sParam)
|
||||
{
|
||||
foreach ($aDataToParse as $sParam) {
|
||||
$this->append(Parameter::CreateFromParameterLine($sParam));
|
||||
}
|
||||
|
||||
|
|
@ -79,18 +75,35 @@ class ParameterCollection extends \MailSo\Base\Collection
|
|||
public function ToString(bool $bConvertSpecialsName = false) : string
|
||||
{
|
||||
$aResult = array();
|
||||
foreach ($this as $oParam)
|
||||
{
|
||||
foreach ($this as $oParam) {
|
||||
$sLine = $oParam->ToString($bConvertSpecialsName);
|
||||
if (\strlen($sLine))
|
||||
{
|
||||
if (\strlen($sLine)) {
|
||||
$aResult[] = $sLine;
|
||||
}
|
||||
}
|
||||
|
||||
return \count($aResult) ? \implode('; ', $aResult) : '';
|
||||
}
|
||||
|
||||
public function __toString() : string
|
||||
{
|
||||
return $this->ToString();
|
||||
}
|
||||
|
||||
/*
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
$aResult = array();
|
||||
foreach ($this as $oParam) {
|
||||
$aResult[$oParam->Name()] = $oParam->Value();
|
||||
}
|
||||
return array(
|
||||
'@Object' => 'Collection/ParameterCollection',
|
||||
'@Collection' => $aResult
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
private function reParseParameters() : void
|
||||
{
|
||||
$aDataToReParse = $this->getArrayCopy();
|
||||
|
|
@ -99,26 +112,22 @@ class ParameterCollection extends \MailSo\Base\Collection
|
|||
$this->Clear();
|
||||
|
||||
$aPreParams = array();
|
||||
foreach ($aDataToReParse as $oParam)
|
||||
{
|
||||
foreach ($aDataToReParse as $oParam) {
|
||||
$aMatch = array();
|
||||
$sParamName = $oParam->Name();
|
||||
|
||||
if (\preg_match('/([^\*]+)\*([\d]{1,2})\*/', $sParamName, $aMatch) && isset($aMatch[1], $aMatch[2])
|
||||
&& \strlen($aMatch[1]) && \is_numeric($aMatch[2]))
|
||||
{
|
||||
if (!isset($aPreParams[$aMatch[1]]))
|
||||
{
|
||||
if (!isset($aPreParams[$aMatch[1]])) {
|
||||
$aPreParams[$aMatch[1]] = array();
|
||||
}
|
||||
|
||||
$sValue = $oParam->Value();
|
||||
|
||||
if (false !== \strpos($sValue, "''"))
|
||||
{
|
||||
if (false !== \strpos($sValue, "''")) {
|
||||
$aValueParts = \explode("''", $sValue, 2);
|
||||
if (\is_array($aValueParts) && 2 === \count($aValueParts) && \strlen($aValueParts[1]))
|
||||
{
|
||||
if (\is_array($aValueParts) && 2 === \count($aValueParts) && \strlen($aValueParts[1])) {
|
||||
$sCharset = $aValueParts[0];
|
||||
$sValue = $aValueParts[1];
|
||||
}
|
||||
|
|
@ -128,17 +137,14 @@ class ParameterCollection extends \MailSo\Base\Collection
|
|||
}
|
||||
else if (\preg_match('/([^\*]+)\*/', $sParamName, $aMatch) && isset($aMatch[1]))
|
||||
{
|
||||
if (!isset($aPreParams[$aMatch[1]]))
|
||||
{
|
||||
if (!isset($aPreParams[$aMatch[1]])) {
|
||||
$aPreParams[$aMatch[1]] = array();
|
||||
}
|
||||
|
||||
$sValue = $oParam->Value();
|
||||
if (false !== \strpos($sValue, "''"))
|
||||
{
|
||||
if (false !== \strpos($sValue, "''")) {
|
||||
$aValueParts = \explode("''", $sValue, 2);
|
||||
if (\is_array($aValueParts) && 2 === \count($aValueParts) && \strlen($aValueParts[1]))
|
||||
{
|
||||
if (\is_array($aValueParts) && 2 === \count($aValueParts) && \strlen($aValueParts[1])) {
|
||||
$sCharset = $aValueParts[0];
|
||||
$sValue = $aValueParts[1];
|
||||
}
|
||||
|
|
@ -152,14 +158,12 @@ class ParameterCollection extends \MailSo\Base\Collection
|
|||
}
|
||||
}
|
||||
|
||||
foreach ($aPreParams as $sName => $aValues)
|
||||
{
|
||||
ksort($aValues);
|
||||
foreach ($aPreParams as $sName => $aValues) {
|
||||
\ksort($aValues);
|
||||
$sResult = \implode(\array_values($aValues));
|
||||
$sResult = \urldecode($sResult);
|
||||
|
||||
if (\strlen($sCharset))
|
||||
{
|
||||
if (\strlen($sCharset)) {
|
||||
$sResult = \MailSo\Base\Utils::ConvertEncoding($sResult,
|
||||
$sCharset, \MailSo\Base\Enumerations\Charset::UTF_8);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ abstract class Parser
|
|||
POS_SUBPARTS = 3,
|
||||
POS_CLOSE_BOUNDARY = 4;
|
||||
|
||||
private static $LineParts = [];
|
||||
private static array $LineParts = [];
|
||||
|
||||
protected static function writeBody(Part $oPart, string $sBuffer) : void
|
||||
{
|
||||
|
|
@ -59,21 +59,18 @@ abstract class Parser
|
|||
|
||||
$oMimePart = null;
|
||||
$sFirstNotNullCharset = null;
|
||||
foreach (static::$LineParts as /* @var $oMimePart Part */ $oMimePart)
|
||||
{
|
||||
foreach (static::$LineParts as /* @var $oMimePart Part */ $oMimePart) {
|
||||
$sCharset = $oMimePart->HeaderCharset();
|
||||
if (\strlen($sCharset))
|
||||
{
|
||||
if (\strlen($sCharset)) {
|
||||
$sFirstNotNullCharset = $sCharset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$sFirstNotNullCharset = (null !== $sFirstNotNullCharset)
|
||||
? $sFirstNotNullCharset : Part::$DefaultCharset;
|
||||
? $sFirstNotNullCharset : \MailSo\Base\Enumerations\Charset::ISO_8859_1;
|
||||
|
||||
foreach (static::$LineParts as /* @var $oMimePart Part */ $oMimePart)
|
||||
{
|
||||
foreach (static::$LineParts as /* @var $oMimePart Part */ $oMimePart) {
|
||||
$sHeaderCharset = $oMimePart->HeaderCharset();
|
||||
$oMimePart->Headers->SetParentCharset($sHeaderCharset);
|
||||
}
|
||||
|
|
@ -93,53 +90,38 @@ abstract class Parser
|
|||
$sCurrentBoundary = '';
|
||||
$bIsBoundaryCheck = false;
|
||||
$aHeadersLines = array();
|
||||
while (true)
|
||||
{
|
||||
if (!$bNotFirstRead)
|
||||
{
|
||||
while (true) {
|
||||
if (!$bNotFirstRead) {
|
||||
$sPrevBuffer = $sBuffer;
|
||||
$sBuffer = '';
|
||||
}
|
||||
|
||||
if (!$bIsOef && !\feof($rStreamHandle))
|
||||
{
|
||||
if (!$bNotFirstRead)
|
||||
{
|
||||
if (!$bIsOef && !\feof($rStreamHandle)) {
|
||||
if (!$bNotFirstRead) {
|
||||
$sBuffer = \fread($rStreamHandle, 8192);
|
||||
if (false === $sBuffer)
|
||||
{
|
||||
if (false === $sBuffer) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$bNotFirstRead = false;
|
||||
}
|
||||
}
|
||||
else if ($bIsOef && !\strlen($sBuffer))
|
||||
{
|
||||
} else if ($bIsOef && !\strlen($sBuffer)) {
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$bIsOef = true;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
while (true) {
|
||||
$sCurrentLine = $sPrevBuffer.$sBuffer;
|
||||
if (self::POS_HEADERS === $iParsePosition)
|
||||
{
|
||||
if (self::POS_HEADERS === $iParsePosition) {
|
||||
$iEndLen = 4;
|
||||
$iPos = \strpos($sCurrentLine, "\r\n\r\n", $iOffset);
|
||||
if (false === $iPos)
|
||||
{
|
||||
if (false === $iPos) {
|
||||
$iEndLen = 2;
|
||||
$iPos = \strpos($sCurrentLine, "\n\n", $iOffset);
|
||||
}
|
||||
|
||||
if (false !== $iPos)
|
||||
{
|
||||
if (false !== $iPos) {
|
||||
$aHeadersLines[] = \substr($sCurrentLine, $iOffset, $iPos + $iEndLen - $iOffset);
|
||||
|
||||
$oPart->Headers->Parse(\implode($aHeadersLines))->SetParentCharset($oPart->HeaderCharset());
|
||||
|
|
@ -156,42 +138,31 @@ abstract class Parser
|
|||
$iOffset = $iPos + $iEndLen;
|
||||
$iParsePosition = self::POS_BODY;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$iBufferLen = \strlen($sPrevBuffer);
|
||||
if ($iBufferLen > $iOffset)
|
||||
{
|
||||
if ($iBufferLen > $iOffset) {
|
||||
$aHeadersLines[] = \substr($sPrevBuffer, $iOffset);
|
||||
$iOffset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$iOffset -= $iBufferLen;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (self::POS_BODY === $iParsePosition)
|
||||
{
|
||||
} else if (self::POS_BODY === $iParsePosition) {
|
||||
$iPos = false;
|
||||
$sBoundaryLen = 0;
|
||||
$bIsBoundaryEnd = false;
|
||||
$bCurrentPartBody = false;
|
||||
$bIsBoundaryCheck = \count($aBoundaryStack);
|
||||
|
||||
foreach ($aBoundaryStack as $sKey => $sBoundary)
|
||||
{
|
||||
if (false !== ($iPos = \strpos($sCurrentLine, $sBoundary, $iOffset)))
|
||||
{
|
||||
if ($sCurrentBoundary === $sBoundary)
|
||||
{
|
||||
foreach ($aBoundaryStack as $sKey => $sBoundary) {
|
||||
if (false !== ($iPos = \strpos($sCurrentLine, $sBoundary, $iOffset))) {
|
||||
if ($sCurrentBoundary === $sBoundary) {
|
||||
$bCurrentPartBody = true;
|
||||
}
|
||||
|
||||
$sBoundaryLen = \strlen($sBoundary);
|
||||
if ('--' === \substr($sCurrentLine, $iPos + $sBoundaryLen, 2))
|
||||
{
|
||||
if ('--' === \substr($sCurrentLine, $iPos + $sBoundaryLen, 2)) {
|
||||
$sBoundaryLen += 2;
|
||||
$bIsBoundaryEnd = true;
|
||||
unset($aBoundaryStack[$sKey]);
|
||||
|
|
@ -203,54 +174,42 @@ abstract class Parser
|
|||
}
|
||||
}
|
||||
|
||||
if (false !== $iPos)
|
||||
{
|
||||
if (false !== $iPos) {
|
||||
static::writeBody($oPart, \substr($sCurrentLine, $iOffset, $iPos - $iOffset));
|
||||
$iOffset = $iPos;
|
||||
|
||||
if ($bCurrentPartBody)
|
||||
{
|
||||
if ($bCurrentPartBody) {
|
||||
$iParsePosition = self::POS_SUBPARTS;
|
||||
continue;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$iBufferLen = \strlen($sPrevBuffer);
|
||||
if ($iBufferLen > $iOffset)
|
||||
{
|
||||
if ($iBufferLen > $iOffset) {
|
||||
static::writeBody($oPart, \substr($sPrevBuffer, $iOffset));
|
||||
$iOffset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$iOffset -= $iBufferLen;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (self::POS_SUBPARTS === $iParsePosition)
|
||||
{
|
||||
} else if (self::POS_SUBPARTS === $iParsePosition) {
|
||||
$iPos = false;
|
||||
$iBoundaryLen = 0;
|
||||
$bIsBoundaryEnd = false;
|
||||
$bCurrentPartBody = false;
|
||||
$bIsBoundaryCheck = \count($aBoundaryStack);
|
||||
|
||||
foreach ($aBoundaryStack as $sKey => $sBoundary)
|
||||
{
|
||||
if (false !== ($iPos = \strpos($sCurrentLine, $sBoundary, $iOffset)))
|
||||
{
|
||||
if ($sCurrentBoundary === $sBoundary)
|
||||
{
|
||||
foreach ($aBoundaryStack as $sKey => $sBoundary) {
|
||||
$iPos = \strpos($sCurrentLine, $sBoundary, $iOffset);
|
||||
if (false !== $iPos) {
|
||||
if ($sCurrentBoundary === $sBoundary) {
|
||||
$bCurrentPartBody = true;
|
||||
}
|
||||
|
||||
$iBoundaryLen = \strlen($sBoundary);
|
||||
if ('--' === \substr($sCurrentLine, $iPos + $iBoundaryLen, 2))
|
||||
{
|
||||
if ('--' === \substr($sCurrentLine, $iPos + $iBoundaryLen, 2)) {
|
||||
$iBoundaryLen += 2;
|
||||
$bIsBoundaryEnd = true;
|
||||
unset($aBoundaryStack[$sKey]);
|
||||
|
|
@ -261,8 +220,7 @@ abstract class Parser
|
|||
}
|
||||
}
|
||||
|
||||
if (false !== $iPos && $bCurrentPartBody)
|
||||
{
|
||||
if (false !== $iPos && $bCurrentPartBody) {
|
||||
$iOffset = $iPos + $iBoundaryLen;
|
||||
|
||||
$oSubPart = new Part;
|
||||
|
|
@ -274,39 +232,27 @@ abstract class Parser
|
|||
static::$LineParts[] = $oSubPart;
|
||||
//$iParsePosition = self::POS_HEADERS;
|
||||
unset($oSubPart);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\strlen($sPrevBuffer))
|
||||
{
|
||||
if (self::POS_HEADERS === $iParsePosition)
|
||||
{
|
||||
if (\strlen($sPrevBuffer)) {
|
||||
if (self::POS_HEADERS === $iParsePosition) {
|
||||
$aHeadersLines[] = ($iOffset < \strlen($sPrevBuffer))
|
||||
? \substr($sPrevBuffer, $iOffset)
|
||||
: $sPrevBuffer;
|
||||
|
||||
$oPart->Headers->Parse(\implode($aHeadersLines))->SetParentCharset($oPart->HeaderCharset());
|
||||
$aHeadersLines = array();
|
||||
} else if (!$bIsBoundaryCheck && self::POS_BODY === $iParsePosition) {
|
||||
static::writeBody($oPart, ($iOffset < \strlen($sPrevBuffer))
|
||||
? \substr($sPrevBuffer, $iOffset) : $sPrevBuffer);
|
||||
}
|
||||
else if (self::POS_BODY === $iParsePosition)
|
||||
{
|
||||
if (!$bIsBoundaryCheck)
|
||||
{
|
||||
static::writeBody($oPart, ($iOffset < \strlen($sPrevBuffer))
|
||||
? \substr($sPrevBuffer, $iOffset) : $sPrevBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self::POS_HEADERS === $iParsePosition && \count($aHeadersLines))
|
||||
{
|
||||
} else {
|
||||
if (self::POS_HEADERS === $iParsePosition && \count($aHeadersLines)) {
|
||||
$oPart->Headers->Parse(\implode($aHeadersLines))->SetParentCharset($oPart->HeaderCharset());
|
||||
$aHeadersLines = array();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,15 +17,7 @@ namespace MailSo\Mime;
|
|||
*/
|
||||
class Part
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $DefaultCharset = \MailSo\Base\Enumerations\Charset::ISO_8859_1;
|
||||
|
||||
/**
|
||||
* @var HeaderCollection
|
||||
*/
|
||||
public $Headers;
|
||||
public HeaderCollection $Headers;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
|
|
@ -37,10 +29,7 @@ class Part
|
|||
*/
|
||||
public $Raw = null;
|
||||
|
||||
/**
|
||||
* @var PartCollection
|
||||
*/
|
||||
public $SubParts;
|
||||
public PartCollection $SubParts;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
|
|
@ -63,29 +52,14 @@ class Part
|
|||
return \trim(\strtolower($this->Headers->ValueByName(Enumerations\Header::CONTENT_TYPE)));
|
||||
}
|
||||
|
||||
public function ContentID() : string
|
||||
{
|
||||
return \trim($this->Headers->ValueByName(Enumerations\Header::CONTENT_ID));
|
||||
}
|
||||
|
||||
public function ContentLocation() : string
|
||||
{
|
||||
return \trim($this->Headers->ValueByName(Enumerations\Header::CONTENT_LOCATION));
|
||||
}
|
||||
|
||||
public function IsFlowedFormat() : bool
|
||||
{
|
||||
$bResult = false;
|
||||
if ($this->Headers)
|
||||
{
|
||||
$bResult = 'flowed' === \trim(\strtolower($this->Headers->ParameterValue(
|
||||
Enumerations\Header::CONTENT_TYPE,
|
||||
Enumerations\Parameter::FORMAT)));
|
||||
$bResult = 'flowed' === \trim(\strtolower($this->Headers->ParameterValue(
|
||||
Enumerations\Header::CONTENT_TYPE,
|
||||
Enumerations\Parameter::FORMAT)));
|
||||
|
||||
if ($bResult && \in_array($this->MailEncodingName(), array('base64', 'quoted-printable')))
|
||||
{
|
||||
$bResult = false;
|
||||
}
|
||||
if ($bResult && \in_array($this->MailEncodingName(), array('base64', 'quoted-printable'))) {
|
||||
$bResult = false;
|
||||
}
|
||||
|
||||
return $bResult;
|
||||
|
|
@ -93,19 +67,14 @@ class Part
|
|||
|
||||
public function FileName() : string
|
||||
{
|
||||
$sResult = '';
|
||||
if ($this->Headers)
|
||||
{
|
||||
$sResult = \trim($this->Headers->ParameterValue(
|
||||
Enumerations\Header::CONTENT_DISPOSITION,
|
||||
Enumerations\Parameter::FILENAME));
|
||||
$sResult = \trim($this->Headers->ParameterValue(
|
||||
Enumerations\Header::CONTENT_DISPOSITION,
|
||||
Enumerations\Parameter::FILENAME));
|
||||
|
||||
if (!\strlen($sResult))
|
||||
{
|
||||
$sResult = \trim($this->Headers->ParameterValue(
|
||||
Enumerations\Header::CONTENT_TYPE,
|
||||
Enumerations\Parameter::NAME));
|
||||
}
|
||||
if (!\strlen($sResult)) {
|
||||
$sResult = \trim($this->Headers->ParameterValue(
|
||||
Enumerations\Header::CONTENT_TYPE,
|
||||
Enumerations\Parameter::NAME));
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace MailSo\Mime;
|
|||
*/
|
||||
class PartCollection extends \MailSo\Base\Collection
|
||||
{
|
||||
protected $sBoundary;
|
||||
protected string $sBoundary = '';
|
||||
|
||||
public function append($oPart, bool $bToTop = false) : void
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,66 +15,84 @@ namespace MailSo\Net;
|
|||
* @category MailSo
|
||||
* @package Net
|
||||
*/
|
||||
class ConnectSettings
|
||||
class ConnectSettings implements \JsonSerializable
|
||||
{
|
||||
public
|
||||
$host,
|
||||
public string $host;
|
||||
|
||||
$port,
|
||||
public int $port;
|
||||
|
||||
// none, TLS, STARTTLS
|
||||
$type = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT,
|
||||
/**
|
||||
* stream timeout in seconds
|
||||
*/
|
||||
public int $timeout = 10;
|
||||
|
||||
// https://www.php.net/context.ssl
|
||||
$ssl = [
|
||||
// 'peer_name' => '',
|
||||
// 'peer_fingerprint' => '', // string | array
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
'allow_self_signed' => false,
|
||||
// 'cafile' => '',
|
||||
// 'capath' => '',
|
||||
// none, TLS, STARTTLS
|
||||
public int $type = Enumerations\ConnectionSecurityType::AUTO_DETECT;
|
||||
// public int $type = Enumerations\ConnectionSecurityType::NONE;
|
||||
|
||||
// 'ciphers' => 'HIGH:!SSLv2:!SSLv3',
|
||||
'SNI_enabled' => true,
|
||||
'disable_compression' => true,
|
||||
'security_level' => 1,
|
||||
public SSLContext $ssl;
|
||||
// public bool $tls_weak = false;
|
||||
|
||||
// 'local_cert' => '',
|
||||
// 'local_pk' => '',
|
||||
// 'passphrase' => '',
|
||||
// Authentication settings use by all child classes
|
||||
public bool $useAuth = true;
|
||||
public bool $shortLogin = false;
|
||||
public array $SASLMechanisms = [
|
||||
// https://github.com/the-djmaze/snappymail/issues/182
|
||||
'SCRAM-SHA3-512',
|
||||
'SCRAM-SHA-512',
|
||||
'SCRAM-SHA-256',
|
||||
'SCRAM-SHA-1',
|
||||
// 'CRAM-MD5',
|
||||
'PLAIN',
|
||||
'LOGIN'
|
||||
];
|
||||
public string $Login = '';
|
||||
public string $Password = '';
|
||||
public string $ProxyAuthUser = '';
|
||||
public string $ProxyAuthPassword = '';
|
||||
|
||||
// 'verify_depth' => 0,
|
||||
// 'capture_peer_cert' => false,
|
||||
// 'capture_peer_cert_chain' => false,
|
||||
];
|
||||
|
||||
function __construct()
|
||||
public function __construct()
|
||||
{
|
||||
// TODO: This should be moved to \RainLoop\Model\Domain
|
||||
$oConfig = \RainLoop\API::Config();
|
||||
$this->ssl['verify_peer'] = !!$oConfig->Get('ssl', 'verify_certificate', false);
|
||||
$this->ssl['verify_peer_name'] = !!$oConfig->Get('ssl', 'verify_certificate', false);
|
||||
$this->ssl['allow_self_signed'] = !!$oConfig->Get('ssl', 'allow_self_signed', true);
|
||||
$this->ssl['security_level'] = (int) $oConfig->Get('ssl', 'security_level', 1);
|
||||
// $this->ssl['local_cert'] = (string) $oConfig->Get('ssl', 'client_cert', '');
|
||||
// $this->ssl['cafile'] = (string) $oConfig->Get('ssl', 'cafile', '');
|
||||
// $this->ssl['capath'] = (string) $oConfig->Get('ssl', 'capath', '');
|
||||
$this->ssl = new SSLContext;
|
||||
}
|
||||
|
||||
public static function Host() : string
|
||||
{
|
||||
return \SnappyMail\IDN::toAscii($this->host);
|
||||
}
|
||||
|
||||
public static function fromArray(array $aSettings) : self
|
||||
{
|
||||
$object = new self;
|
||||
$object->host = $aSettings['Host'];
|
||||
$object->port = $aSettings['Port'];
|
||||
$object->type = $aSettings['Secure'];
|
||||
$object->ssl['verify_peer'] = !!$aSettings['VerifySsl'];
|
||||
$object->ssl['verify_peer_name'] = !!$aSettings['VerifySsl'];
|
||||
$object->ssl['allow_self_signed'] = !!$aSettings['AllowSelfSigned'];
|
||||
if (!empty($aSettings['ClientCert'])) {
|
||||
$object->ssl['local_cert'] = $aSettings['ClientCert'];
|
||||
$object = new static;
|
||||
$object->host = $aSettings['host'];
|
||||
$object->port = $aSettings['port'];
|
||||
$object->type = isset($aSettings['type']) ? $aSettings['type'] : $aSettings['secure'];
|
||||
if (isset($aSettings['timeout'])) {
|
||||
$object->timeout = $aSettings['timeout'];
|
||||
}
|
||||
$object->shortLogin = !empty($aSettings['shortLogin']);
|
||||
$object->ssl = SSLContext::fromArray($aSettings['ssl'] ?? []);
|
||||
if (isset($aSettings['sasl'])) {
|
||||
$object->SASLMechanisms = $aSettings['sasl'];
|
||||
}
|
||||
// $object->tls_weak = !empty($aSettings['tls_weak']);
|
||||
return $object;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return array(
|
||||
// '@Object' => 'Object/ConnectSettings',
|
||||
'host' => $this->host,
|
||||
'port' => $this->port,
|
||||
'type' => $this->type,
|
||||
'timeout' => $this->timeout,
|
||||
'shortLogin' => $this->shortLogin,
|
||||
'sasl' => $this->SASLMechanisms,
|
||||
'ssl' => $this->ssl
|
||||
// 'tls_weak' => $this->tls_weak
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,15 +18,9 @@ namespace MailSo\Net\Exceptions;
|
|||
*/
|
||||
class SocketCanNotConnectToHostException extends ConnectionException
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sSocketMessage;
|
||||
private string $sSocketMessage;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iSocketCode;
|
||||
private int $iSocketCode;
|
||||
|
||||
public function __construct(string $sSocketMessage = '', int $iSocketCode = 0, string $sMessage = '', int $iCode = 0, ?\Throwable $oPrevious = null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,65 +17,26 @@ namespace MailSo\Net;
|
|||
*/
|
||||
abstract class NetClient
|
||||
{
|
||||
use \MailSo\Log\Inherit;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $rConnect = null;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bUnreadBuffer = false;
|
||||
private bool $bUnreadBuffer = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $bRunningCallback = false;
|
||||
protected bool $bRunningCallback = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sResponseBuffer = '';
|
||||
private string $sConnectedHost = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::NONE;
|
||||
private bool $ssl = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sConnectedHost = '';
|
||||
private int $iConnectTimeOut = 10;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iConnectedPort = 0;
|
||||
private float $iStartConnectTime = 0;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bSecure = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iConnectTimeOut = 10;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iSocketTimeOut = 10;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iStartConnectTime = 0;
|
||||
|
||||
/**
|
||||
* @var \MailSo\Log\Logger
|
||||
*/
|
||||
protected $oLogger = null;
|
||||
public ConnectSettings $Settings;
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
|
|
@ -91,15 +52,9 @@ abstract class NetClient
|
|||
return $this->sConnectedHost;
|
||||
}
|
||||
|
||||
public function GetConnectedPort() : int
|
||||
public function SetTimeOuts(int $iConnectTimeOut = 10) : void
|
||||
{
|
||||
return $this->iConnectedPort;
|
||||
}
|
||||
|
||||
public function SetTimeOuts(int $iConnectTimeOut = 10, int $iSocketTimeOut = 10) : void
|
||||
{
|
||||
$this->iConnectTimeOut = max(5, $iConnectTimeOut);
|
||||
$this->iSocketTimeOut = max(5, $iSocketTimeOut);
|
||||
$this->iConnectTimeOut = \max(5, $iConnectTimeOut);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -116,65 +71,56 @@ abstract class NetClient
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Base\Exceptions\InvalidArgumentException
|
||||
* @throws \MailSo\Net\Exceptions\SocketAlreadyConnectedException
|
||||
* @throws \MailSo\Net\Exceptions\SocketCanNotConnectToHostException
|
||||
*/
|
||||
public function Connect(ConnectSettings $oSettings) : void
|
||||
{
|
||||
$oSettings->host = \trim($oSettings->host);
|
||||
if (!\strlen($oSettings->host) || !\MailSo\Base\Validator::PortInt($oSettings->port)) {
|
||||
$this->writeLogException(
|
||||
new \MailSo\Base\Exceptions\InvalidArgumentException,
|
||||
\LOG_ERR, true);
|
||||
if (!\strlen($oSettings->host) || 0 > $oSettings->port || 65535 < $oSettings->port) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
||||
}
|
||||
|
||||
if ($this->IsConnected())
|
||||
{
|
||||
$this->writeLogException(
|
||||
new Exceptions\SocketAlreadyConnectedException,
|
||||
\LOG_ERR, true);
|
||||
if ($this->IsConnected()) {
|
||||
$this->writeLogException(new Exceptions\SocketAlreadyConnectedException, \LOG_ERR, false);
|
||||
// $this->Disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->Settings = $oSettings;
|
||||
|
||||
$sErrorStr = '';
|
||||
$iErrorNo = 0;
|
||||
|
||||
$this->sConnectedHost = $oSettings->host;
|
||||
$this->iConnectedPort = $oSettings->port;
|
||||
$this->iSecurityType = $oSettings->type;
|
||||
$this->bSecure = \MailSo\Net\Enumerations\ConnectionSecurityType::UseSSL(
|
||||
$this->iConnectedPort, $this->iSecurityType);
|
||||
|
||||
if (!\preg_match('/^[a-z0-9._]{2,8}:\/\//i', $this->sConnectedHost))
|
||||
{
|
||||
$this->sConnectedHost = ($this->bSecure ? 'ssl://' : 'tcp://').$this->sConnectedHost;
|
||||
// $this->sConnectedHost = ($this->bSecure ? 'ssl://' : '').$this->sConnectedHost;
|
||||
$this->ssl = \MailSo\Net\Enumerations\ConnectionSecurityType::UseSSL($this->Settings->port, $this->Settings->type);
|
||||
|
||||
if (!\preg_match('/^[a-z0-9._]{2,8}:\/\//i', $this->sConnectedHost)) {
|
||||
$this->sConnectedHost = ($this->ssl ? 'ssl://' : 'tcp://') . $this->sConnectedHost;
|
||||
// $this->sConnectedHost = ($this->ssl ? 'ssl://' : '') . $this->sConnectedHost;
|
||||
}
|
||||
|
||||
if (!$this->bSecure && \MailSo\Net\Enumerations\ConnectionSecurityType::SSL === $this->iSecurityType)
|
||||
{
|
||||
if (!$this->ssl && \MailSo\Net\Enumerations\ConnectionSecurityType::SSL === $this->Settings->type) {
|
||||
$this->writeLogException(
|
||||
new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('SSL isn\'t supported: ('.\implode(', ', \stream_get_transports()).')'),
|
||||
\LOG_ERR, true);
|
||||
\LOG_ERR);
|
||||
}
|
||||
|
||||
$this->iStartConnectTime = \microtime(true);
|
||||
$this->writeLog('Start connection to "'.$this->sConnectedHost.':'.$this->iConnectedPort.'"');
|
||||
$this->writeLog('Start connection to "'.$this->sConnectedHost.':'.$this->Settings->port.'"');
|
||||
|
||||
$aStreamContextSettings = array(
|
||||
'ssl' => $oSettings->ssl
|
||||
);
|
||||
|
||||
\MailSo\Hooks::Run('Net.NetClient.StreamContextSettings/Filter', array(&$aStreamContextSettings));
|
||||
|
||||
$rStreamContext = \stream_context_create($aStreamContextSettings);
|
||||
$rStreamContext = \stream_context_create(array(
|
||||
'ssl' => $oSettings->ssl->jsonSerialize()
|
||||
));
|
||||
|
||||
\set_error_handler(array($this, 'capturePhpErrorWithException'));
|
||||
|
||||
try
|
||||
{
|
||||
$this->rConnect = \stream_socket_client($this->sConnectedHost.':'.$this->iConnectedPort,
|
||||
$this->rConnect = \stream_socket_client($this->sConnectedHost.':'.$this->Settings->port,
|
||||
$iErrorNo, $sErrorStr, $this->iConnectTimeOut, STREAM_CLIENT_CONNECT, $rStreamContext);
|
||||
}
|
||||
catch (\Throwable $oExc)
|
||||
|
|
@ -187,23 +133,19 @@ abstract class NetClient
|
|||
|
||||
$this->writeLog('Connect ('.($this->rConnect ? 'success' : 'failed').')');
|
||||
|
||||
if (!$this->rConnect)
|
||||
{
|
||||
if (!$this->rConnect) {
|
||||
$this->writeLogException(
|
||||
new Exceptions\SocketCanNotConnectToHostException(
|
||||
\MailSo\Base\Locale::ConvertSystemString($sErrorStr), (int) $iErrorNo,
|
||||
'Can\'t connect to host "'.$this->sConnectedHost.':'.$this->iConnectedPort.'"'
|
||||
), \LOG_NOTICE, true);
|
||||
'Can\'t connect to host "'.$this->sConnectedHost.':'.$this->Settings->port.'"'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->writeLog((\microtime(true) - $this->iStartConnectTime).' (raw connection)', \LOG_DEBUG);
|
||||
|
||||
if ($this->rConnect)
|
||||
{
|
||||
if (\MailSo\Base\Utils::FunctionCallable('stream_set_timeout'))
|
||||
{
|
||||
\stream_set_timeout($this->rConnect, $this->iSocketTimeOut);
|
||||
}
|
||||
if ($this->rConnect && \MailSo\Base\Utils::FunctionCallable('stream_set_timeout')) {
|
||||
\stream_set_timeout($this->rConnect, \max(5, $oSettings->timeout));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,27 +154,21 @@ abstract class NetClient
|
|||
return $this->rConnect && !empty(\stream_get_meta_data($this->rConnect)['crypto']);
|
||||
}
|
||||
|
||||
public function EnableCrypto(bool $insecure = false)
|
||||
public function EnableCrypto() : void
|
||||
{
|
||||
$bError = true;
|
||||
$bSuccess = false;
|
||||
if ($this->rConnect && \MailSo\Base\Utils::FunctionCallable('stream_socket_enable_crypto')) {
|
||||
$crypto_method = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
|
||||
if (\defined('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT') && OPENSSL_VERSION_NUMBER >= 0x10101000) {
|
||||
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
|
||||
}
|
||||
if ($insecure) {
|
||||
$crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
|
||||
}
|
||||
if (\stream_socket_enable_crypto($this->rConnect, true, $crypto_method)) {
|
||||
$bError = false;
|
||||
}
|
||||
// if ($this->Settings->tls_weak) {
|
||||
// $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
|
||||
// }
|
||||
$bSuccess = \stream_socket_enable_crypto($this->rConnect, true, $crypto_method);
|
||||
}
|
||||
|
||||
if ($bError) {
|
||||
$this->writeLogException(
|
||||
new \MailSo\RuntimeException('Cannot enable STARTTLS.'),
|
||||
\LOG_ERR, true);
|
||||
}
|
||||
$bSuccess || $this->writeLogException(new \MailSo\RuntimeException('Cannot enable STARTTLS.'), \LOG_ERR);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -241,29 +177,26 @@ abstract class NetClient
|
|||
*/
|
||||
public function Disconnect() : void
|
||||
{
|
||||
if ($this->rConnect)
|
||||
{
|
||||
if (!$this->bUnreadBuffer && !$this->bRunningCallback)
|
||||
{
|
||||
if ($this->rConnect) {
|
||||
if (!$this->bUnreadBuffer && !$this->bRunningCallback) {
|
||||
$this->Logout();
|
||||
}
|
||||
|
||||
$bResult = \fclose($this->rConnect);
|
||||
|
||||
$this->writeLog('Disconnected from "'.$this->sConnectedHost.':'.$this->iConnectedPort.'" ('.
|
||||
$this->writeLog('Disconnected from "'.$this->sConnectedHost.':'.$this->Settings->port.'" ('.
|
||||
(($bResult) ? 'success' : 'unsuccess').')');
|
||||
|
||||
if ($this->iStartConnectTime)
|
||||
{
|
||||
if ($this->iStartConnectTime) {
|
||||
$this->writeLog((\microtime(true) - $this->iStartConnectTime).' (net session)', \LOG_DEBUG);
|
||||
|
||||
$this->iStartConnectTime = 0;
|
||||
}
|
||||
|
||||
$this->rConnect = null;
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function supportsAuthType(string $sasl_type) : bool;
|
||||
// abstract public function Login(ConnectSettings $oSettings) : self;
|
||||
abstract public function Logout() : void;
|
||||
|
||||
public function IsConnected(bool $bThrowExceptionOnFalse = false) : bool
|
||||
|
|
@ -272,21 +205,11 @@ abstract class NetClient
|
|||
return true;
|
||||
}
|
||||
if ($bThrowExceptionOnFalse) {
|
||||
$this->writeLogException(
|
||||
new Exceptions\SocketConnectionDoesNotAvailableException,
|
||||
\LOG_ERR, true);
|
||||
$this->writeLogException(new Exceptions\SocketConnectionDoesNotAvailableException, \LOG_ERR);
|
||||
}
|
||||
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')
|
||||
|
|
@ -300,7 +223,7 @@ abstract class NetClient
|
|||
protected function sendRaw(string $sRaw, bool $bWriteToLog = true, string $sFakeRaw = '') : void
|
||||
{
|
||||
if ($this->bUnreadBuffer) {
|
||||
$this->writeLogException(new Exceptions\SocketUnreadBufferException, \LOG_ERR, true);
|
||||
$this->writeLogException(new Exceptions\SocketUnreadBufferException, \LOG_ERR);
|
||||
}
|
||||
|
||||
$sRaw .= "\r\n";
|
||||
|
|
@ -311,13 +234,10 @@ abstract class NetClient
|
|||
}
|
||||
|
||||
$mResult = \fwrite($this->rConnect, $sRaw);
|
||||
if (false === $mResult)
|
||||
{
|
||||
if (false === $mResult) {
|
||||
$this->IsConnected(true);
|
||||
$this->writeLogException(new Exceptions\SocketWriteException, \LOG_ERR, true);
|
||||
}
|
||||
else if ($bWriteToLog)
|
||||
{
|
||||
$this->writeLogException(new Exceptions\SocketWriteException, \LOG_ERR);
|
||||
} else if ($bWriteToLog) {
|
||||
$this->writeLogWithCrlf('> '.($bFake ? $sFakeRaw : $sRaw));
|
||||
}
|
||||
}
|
||||
|
|
@ -326,112 +246,69 @@ abstract class NetClient
|
|||
* @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException
|
||||
* @throws \MailSo\Net\Exceptions\SocketReadException
|
||||
*/
|
||||
protected function getNextBuffer(?int $iReadLen = null, bool $bForceLogin = false) : void
|
||||
protected function getNextBuffer(?int $iReadLen = null) : ?string
|
||||
{
|
||||
if (null === $iReadLen)
|
||||
{
|
||||
$this->sResponseBuffer = \fgets($this->rConnect);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->sResponseBuffer = '';
|
||||
if (null === $iReadLen) {
|
||||
$sResponseBuffer = \fgets($this->rConnect);
|
||||
} else {
|
||||
$sResponseBuffer = '';
|
||||
$iRead = $iReadLen;
|
||||
while (0 < $iRead)
|
||||
{
|
||||
while (0 < $iRead) {
|
||||
$sAddRead = \fread($this->rConnect, $iRead);
|
||||
if (false === $sAddRead)
|
||||
{
|
||||
$this->sResponseBuffer = false;
|
||||
if (false === $sAddRead) {
|
||||
$sResponseBuffer = false;
|
||||
break;
|
||||
}
|
||||
|
||||
$this->sResponseBuffer .= $sAddRead;
|
||||
$sResponseBuffer .= $sAddRead;
|
||||
$iRead -= \strlen($sAddRead);
|
||||
}
|
||||
}
|
||||
|
||||
if (false === $this->sResponseBuffer)
|
||||
{
|
||||
if (false === $sResponseBuffer) {
|
||||
$this->IsConnected(true);
|
||||
$this->bUnreadBuffer = true;
|
||||
|
||||
$aSocketStatus = \stream_get_meta_data($this->rConnect);
|
||||
if (isset($aSocketStatus['timed_out']) && $aSocketStatus['timed_out'])
|
||||
{
|
||||
$this->writeLogException(
|
||||
new Exceptions\SocketReadTimeoutException,
|
||||
\LOG_ERR, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isset($aSocketStatus['timed_out']) && $aSocketStatus['timed_out']) {
|
||||
$this->writeLogException(new Exceptions\SocketReadTimeoutException, \LOG_ERR);
|
||||
} else {
|
||||
$this->writeLog('Stream Meta: '.\print_r($aSocketStatus, true), \LOG_ERR);
|
||||
$this->writeLogException(new Exceptions\SocketReadException, \LOG_ERR);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->writeLogException(
|
||||
new Exceptions\SocketReadException,
|
||||
\LOG_ERR, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$iReadedLen = \strlen($this->sResponseBuffer);
|
||||
if (null === $iReadLen || $bForceLogin)
|
||||
{
|
||||
$iLimit = 5000; // 5KB
|
||||
if ($iLimit < $iReadedLen)
|
||||
{
|
||||
$this->writeLogWithCrlf('[cutted:'.$iReadedLen.'] < '.\substr($this->sResponseBuffer, 0, $iLimit).'...');
|
||||
}
|
||||
else
|
||||
{
|
||||
// $this->writeLogWithCrlf('< '.$this->sResponseBuffer, //.' ['.$iReadedLen.']',
|
||||
$this->writeLogWithCrlf('< '.$this->sResponseBuffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->writeLog('Received '.$iReadedLen.'/'.$iReadLen.' bytes.');
|
||||
}
|
||||
$iReadBytes = \strlen($sResponseBuffer);
|
||||
// $iReadLen && $this->writeLog('Received '.$iReadBytes.'/'.$iReadLen.' bytes.');
|
||||
$iLimit = 5000; // 5KB
|
||||
if ($iLimit < $iReadBytes) {
|
||||
$this->writeLogWithCrlf('[cutted:'.$iReadBytes.'] < '.\substr($sResponseBuffer, 0, $iLimit).'...');
|
||||
} else {
|
||||
$this->writeLogWithCrlf('< '.$sResponseBuffer);
|
||||
}
|
||||
|
||||
return $sResponseBuffer;
|
||||
}
|
||||
|
||||
abstract function getLogName() : string;
|
||||
|
||||
protected function writeLog(string $sDesc, int $iDescType = \LOG_INFO) : void
|
||||
{
|
||||
$this->oLogger && $this->oLogger->Write($sDesc, $iDescType, $this->getLogName());
|
||||
$this->logWrite($sDesc, $iDescType, $this->getLogName());
|
||||
}
|
||||
|
||||
protected function writeLogWithCrlf(string $sDesc) : void
|
||||
{
|
||||
$this->oLogger && $this->oLogger->Write($sDesc, \LOG_INFO, $this->getLogName(), true, true);
|
||||
$this->logWrite($sDesc, \LOG_INFO, $this->getLogName(), true, true);
|
||||
}
|
||||
|
||||
protected function writeLogException(\Throwable $oException,
|
||||
int $iDescType = \LOG_NOTICE, bool $bThrowException = false) : void
|
||||
protected function writeLogException(\Throwable $oException, int $iDescType = \LOG_NOTICE, bool $bThrowException = true) : void
|
||||
{
|
||||
if ($this->oLogger)
|
||||
{
|
||||
if ($oException instanceof Exceptions\SocketCanNotConnectToHostException)
|
||||
{
|
||||
$this->oLogger->Write('Socket: ['.$oException->getSocketCode().'] '.$oException->getSocketMessage(), $iDescType, $this->getLogName());
|
||||
}
|
||||
|
||||
$this->oLogger->WriteException($oException, $iDescType, $this->getLogName());
|
||||
if ($oException instanceof Exceptions\SocketCanNotConnectToHostException) {
|
||||
$this->logWrite('Socket: ['.$oException->getSocketCode().'] '.$oException->getSocketMessage(), $iDescType, $this->getLogName());
|
||||
}
|
||||
|
||||
if ($bThrowException)
|
||||
{
|
||||
$this->logException($oException, $iDescType, $this->getLogName());
|
||||
if ($bThrowException) {
|
||||
throw $oException;
|
||||
}
|
||||
}
|
||||
|
||||
public function SetLogger(\MailSo\Log\Logger $oLogger) : void
|
||||
{
|
||||
$this->oLogger = $oLogger;
|
||||
}
|
||||
|
||||
public function Logger() : ?\MailSo\Log\Logger
|
||||
{
|
||||
return $this->oLogger;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
73
snappymail/v/0.0.0/app/libraries/MailSo/Net/SSLContext.php
Normal file
73
snappymail/v/0.0.0/app/libraries/MailSo/Net/SSLContext.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2022 DJMaze
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.php.net/context.ssl
|
||||
*/
|
||||
|
||||
namespace MailSo\Net;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Net
|
||||
*/
|
||||
class SSLContext implements \JsonSerializable
|
||||
{
|
||||
// public string $peer_name = '';
|
||||
// public string $peer_fingerprint = '';
|
||||
public bool $verify_peer = true;
|
||||
public bool $verify_peer_name = true;
|
||||
public bool $allow_self_signed = false;
|
||||
public string $cafile = '';
|
||||
public string $capath = '';
|
||||
// public string $ciphers = 'HIGH:!SSLv2:!SSLv3';
|
||||
public bool $SNI_enabled = true;
|
||||
public bool $disable_compression = true;
|
||||
public int $security_level = 1;
|
||||
public string $local_cert = '';
|
||||
// public string $local_pk = '';
|
||||
// public string $passphrase = '';
|
||||
// public int $verify_depth = 0;
|
||||
// public bool $capture_peer_cert = false;
|
||||
// public bool $capture_peer_cert_chain = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$oConfig = \RainLoop\API::Config();
|
||||
$this->verify_peer = !!$oConfig->Get('ssl', 'verify_certificate', true);
|
||||
$this->verify_peer_name = !!$oConfig->Get('ssl', 'verify_certificate', true);
|
||||
$this->allow_self_signed = !!$oConfig->Get('ssl', 'allow_self_signed', false);
|
||||
$this->cafile = \trim($oConfig->Get('ssl', 'cafile', ''));
|
||||
$this->capath = \trim($oConfig->Get('ssl', 'capath', ''));
|
||||
$this->disable_compression = !!$oConfig->Get('ssl', 'disable_compression', true);
|
||||
$this->security_level = (int) $oConfig->Get('ssl', 'security_level', 1);
|
||||
$this->local_cert = \trim($oConfig->Get('ssl', 'local_cert', ''));
|
||||
}
|
||||
|
||||
public static function fromArray(array $settings) : self
|
||||
{
|
||||
$object = new static;
|
||||
foreach ($settings as $key => $value) {
|
||||
$object->$key = $value;
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
$aResult = \get_object_vars($this);
|
||||
// $aResult['@Object'] = 'Object/SSLContext';
|
||||
return \array_filter(
|
||||
$aResult,
|
||||
function($var){return !\is_string($var) || \strlen($var);}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -18,10 +18,7 @@ namespace MailSo\Sieve\Exceptions;
|
|||
*/
|
||||
class ResponseException extends \MailSo\RuntimeException
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aResponses;
|
||||
private array $aResponses;
|
||||
|
||||
public function __construct(array $aResponses = array(), string $sMessage = '', int $iCode = 0, ?\Throwable $oPrevious = null)
|
||||
{
|
||||
|
|
@ -37,7 +34,7 @@ class ResponseException extends \MailSo\RuntimeException
|
|||
|
||||
public function GetLastResponse() : ?\MailSo\Sieve\Response
|
||||
{
|
||||
$iCnt = count($this->aResponses);
|
||||
$iCnt = \count($this->aResponses);
|
||||
return $iCnt ? $this->aResponses[$iCnt - 1] : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
54
snappymail/v/0.0.0/app/libraries/MailSo/Sieve/Settings.php
Normal file
54
snappymail/v/0.0.0/app/libraries/MailSo/Sieve/Settings.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2022 DJMaze
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo\Sieve;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Net
|
||||
*/
|
||||
class Settings extends \MailSo\Net\ConnectSettings
|
||||
{
|
||||
public int $port = 4190;
|
||||
|
||||
public bool $enabled = false;
|
||||
|
||||
public bool $authPlainLiteral = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$oConfig = \RainLoop\API::Config();
|
||||
$this->authPlainLiteral = !$oConfig->Get('labs', 'sieve_auth_plain_initial', true);
|
||||
}
|
||||
|
||||
public static function fromArray(array $aSettings) : self
|
||||
{
|
||||
$object = parent::fromArray($aSettings);
|
||||
$object->enabled = !empty($aSettings['enabled']);
|
||||
// $object->authPlainLiteral = !empty($aSettings['authPlainLiteral']);
|
||||
return $object;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return \array_merge(
|
||||
parent::jsonSerialize(),
|
||||
[
|
||||
// '@Object' => 'Object/SmtpSettings',
|
||||
'enabled' => $this->enabled,
|
||||
// 'authPlainLiteral' => $this->authPlainLiteral
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -17,48 +17,26 @@ use MailSo\Net\Enumerations\ConnectionSecurityType;
|
|||
* @category MailSo
|
||||
* @package Sieve
|
||||
*/
|
||||
class ManageSieveClient extends \MailSo\Net\NetClient
|
||||
class SieveClient extends \MailSo\Net\NetClient
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bIsLoggined = false;
|
||||
private bool $bIsLoggined = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aCapa = array();
|
||||
private array $aCapa = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aAuth = array();
|
||||
private array $aAuth = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aModules = array();
|
||||
private array $aModules = array();
|
||||
|
||||
public function IsSupported(string $sCapa) : bool
|
||||
public function hasCapability(string $sCapa) : bool
|
||||
{
|
||||
return isset($this->aCapa[\strtoupper($sCapa)]);
|
||||
}
|
||||
|
||||
public function IsModuleSupported(string $sModule) : bool
|
||||
{
|
||||
return $this->IsSupported('SIEVE') && \in_array(\strtolower(\trim($sModule)), $this->aModules);
|
||||
}
|
||||
|
||||
public function Modules() : array
|
||||
{
|
||||
return $this->aModules;
|
||||
}
|
||||
|
||||
public function IsAuthSupported(string $sAuth) : bool
|
||||
{
|
||||
return $this->IsSupported('SASL') && \in_array(\strtoupper($sAuth), $this->aAuth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \MailSo\RuntimeException
|
||||
|
|
@ -70,80 +48,81 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
parent::Connect($oSettings);
|
||||
|
||||
$aResponse = $this->parseResponse();
|
||||
$this->validateResponse($aResponse);
|
||||
$this->parseStartupResponse($aResponse);
|
||||
|
||||
if (ConnectionSecurityType::STARTTLS === $this->iSecurityType
|
||||
|| (ConnectionSecurityType::AUTO_DETECT === $this->iSecurityType && $this->IsSupported('STARTTLS'))) {
|
||||
if (ConnectionSecurityType::STARTTLS === $this->Settings->type
|
||||
|| (ConnectionSecurityType::AUTO_DETECT === $this->Settings->type && $this->hasCapability('STARTTLS'))) {
|
||||
$this->StartTLS();
|
||||
}
|
||||
}
|
||||
|
||||
private function StartTLS() : void
|
||||
{
|
||||
if ($this->IsSupported('STARTTLS')) {
|
||||
if ($this->hasCapability('STARTTLS')) {
|
||||
$this->sendRequestWithCheck('STARTTLS');
|
||||
$this->EnableCrypto();
|
||||
$aResponse = $this->parseResponse();
|
||||
$this->validateResponse($aResponse);
|
||||
$this->parseStartupResponse($aResponse);
|
||||
} else {
|
||||
$this->writeLogException(
|
||||
new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'),
|
||||
\LOG_ERR, true);
|
||||
\LOG_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
public function supportsAuthType(string $sasl_type) : bool
|
||||
{
|
||||
return \in_array(\strtoupper($sasl_type), $this->aAuth);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Sieve\Exceptions\*
|
||||
*/
|
||||
public function Login(array $aCredentials) : self
|
||||
public function Login(Settings $oSettings) : self
|
||||
{
|
||||
$sLogin = $aCredentials['Login'];
|
||||
$sPassword = $aCredentials['Password'];
|
||||
if ($this->bIsLoggined) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$sLogin = $oSettings->Login;
|
||||
$sPassword = $oSettings->Password;
|
||||
$sLoginAuthKey = '';
|
||||
if (!\strlen($sLogin) || !\strlen($sPassword))
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \InvalidArgumentException,
|
||||
\LOG_ERR, true);
|
||||
if (!\strlen($sLogin) || !\strlen($sPassword)) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
||||
}
|
||||
|
||||
$type = '';
|
||||
\array_push($aCredentials['SASLMechanisms'], 'PLAIN', 'LOGIN');
|
||||
foreach ($aCredentials['SASLMechanisms'] as $sasl_type) {
|
||||
if ($this->IsAuthSupported($sasl_type) && \SnappyMail\SASL::isSupported($sasl_type)) {
|
||||
$type = $sasl_type;
|
||||
break;
|
||||
if ($this->hasCapability('SASL')) {
|
||||
foreach ($oSettings->SASLMechanisms as $sasl_type) {
|
||||
if (\in_array(\strtoupper($sasl_type), $this->aAuth) && \SnappyMail\SASL::isSupported($sasl_type)) {
|
||||
$type = $sasl_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$type) {
|
||||
if (!$this->Encrypted() && $this->IsSupported('STARTTLS')) {
|
||||
if (!$this->Encrypted() && $this->hasCapability('STARTTLS')) {
|
||||
$this->StartTLS();
|
||||
return $this->Login($aCredentials);
|
||||
return $this->Login($oSettings);
|
||||
}
|
||||
$this->writeLogException(
|
||||
new \MailSo\Sieve\Exceptions\LoginException,
|
||||
\LOG_ERR, true);
|
||||
$this->writeLogException(new \MailSo\Sieve\Exceptions\LoginException, \LOG_ERR);
|
||||
}
|
||||
|
||||
$SASL = \SnappyMail\SASL::factory($type);
|
||||
$SASL->base64 = true;
|
||||
|
||||
$bAuth = false;
|
||||
try
|
||||
{
|
||||
if (0 === \strpos($type, 'SCRAM-'))
|
||||
if (\str_starts_with($type, 'SCRAM-'))
|
||||
{
|
||||
/*
|
||||
$sAuthzid = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), \MailSo\Imap\Enumerations\ResponseType::CONTINUATION);
|
||||
$this->sendRaw($SASL->authenticate($sLogin, $sPassword/*, $sAuthzid* /), true);
|
||||
$sChallenge = $SASL->challenge($this->getResponseValue($this->getResponse(), \MailSo\Imap\Enumerations\ResponseType::CONTINUATION));
|
||||
$this->oLogger && $this->oLogger->AddSecret($sChallenge);
|
||||
$this->logMask($sChallenge);
|
||||
$this->sendRaw($sChallenge);
|
||||
$oResponse = $this->getResponse();
|
||||
$SASL->verify($this->getResponseValue($oResponse));
|
||||
|
|
@ -152,17 +131,16 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
else if ('PLAIN' === $type || 'OAUTHBEARER' === $type || 'XOAUTH2' === $type)
|
||||
{
|
||||
$sAuth = $SASL->authenticate($sLogin, $sPassword, $sLoginAuthKey);
|
||||
$this->oLogger && $this->oLogger->AddSecret($sAuth);
|
||||
$this->logMask($sAuth);
|
||||
|
||||
if ($aCredentials['InitialAuthPlain']) {
|
||||
$this->sendRaw("AUTHENTICATE \"{$type}\" \"{$sAuth}\"");
|
||||
} else {
|
||||
if ($oSettings->authPlainLiteral) {
|
||||
$this->sendRaw("AUTHENTICATE \"{$type}\" {".\strlen($sAuth).'+}');
|
||||
$this->sendRaw($sAuth);
|
||||
} else {
|
||||
$this->sendRaw("AUTHENTICATE \"{$type}\" \"{$sAuth}\"");
|
||||
}
|
||||
|
||||
$aResponse = $this->parseResponse();
|
||||
$this->validateResponse($aResponse);
|
||||
$this->parseStartupResponse($aResponse);
|
||||
$bAuth = true;
|
||||
}
|
||||
|
|
@ -170,7 +148,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
{
|
||||
$sLogin = $SASL->authenticate($sLogin, $sPassword);
|
||||
$sPassword = $SASL->challenge('');
|
||||
$this->oLogger && $this->oLogger->AddSecret($sPassword);
|
||||
$this->logMask($sPassword);
|
||||
|
||||
$this->sendRaw('AUTHENTICATE "LOGIN"');
|
||||
$this->sendRaw('{'.\strlen($sLogin).'+}');
|
||||
|
|
@ -179,7 +157,6 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
$this->sendRaw($sPassword);
|
||||
|
||||
$aResponse = $this->parseResponse();
|
||||
$this->validateResponse($aResponse);
|
||||
$this->parseStartupResponse($aResponse);
|
||||
$bAuth = true;
|
||||
}
|
||||
|
|
@ -187,17 +164,11 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
catch (\MailSo\Sieve\Exceptions\NegativeResponseException $oException)
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \MailSo\Sieve\Exceptions\LoginBadCredentialsException(
|
||||
$oException->GetResponses(), '', 0, $oException),
|
||||
\LOG_ERR, true);
|
||||
new \MailSo\Sieve\Exceptions\LoginBadCredentialsException($oException->GetResponses(), '', 0, $oException),
|
||||
\LOG_ERR);
|
||||
}
|
||||
|
||||
if (!$bAuth)
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \MailSo\Sieve\Exceptions\LoginBadMethodException,
|
||||
\LOG_ERR, true);
|
||||
}
|
||||
$bAuth || $this->writeLogException(new \MailSo\Sieve\Exceptions\LoginBadMethodException, \LOG_ERR);
|
||||
|
||||
$this->bIsLoggined = true;
|
||||
|
||||
|
|
@ -211,8 +182,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function Logout() : void
|
||||
{
|
||||
if ($this->bIsLoggined)
|
||||
{
|
||||
if ($this->bIsLoggined) {
|
||||
$this->sendRequestWithCheck('LOGOUT');
|
||||
$this->bIsLoggined = false;
|
||||
}
|
||||
|
|
@ -227,18 +197,12 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
{
|
||||
$this->sendRequest('LISTSCRIPTS');
|
||||
$aResponse = $this->parseResponse();
|
||||
$this->validateResponse($aResponse);
|
||||
|
||||
$aResult = array();
|
||||
foreach ($aResponse as $sLine)
|
||||
{
|
||||
foreach ($aResponse as $sLine) {
|
||||
$aTokens = $this->parseLine($sLine);
|
||||
if (!$aTokens)
|
||||
{
|
||||
continue;
|
||||
if ($aTokens) {
|
||||
$aResult[$aTokens[0]] = 'ACTIVE' === \substr($sLine, -6);
|
||||
}
|
||||
|
||||
$aResult[$aTokens[0]] = 'ACTIVE' === substr($sLine, -6);
|
||||
}
|
||||
|
||||
return $aResult;
|
||||
|
|
@ -253,7 +217,6 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
{
|
||||
$this->sendRequest('CAPABILITY');
|
||||
$aResponse = $this->parseResponse();
|
||||
$this->validateResponse($aResponse);
|
||||
$this->parseStartupResponse($aResponse);
|
||||
|
||||
return $this->aCapa;
|
||||
|
|
@ -278,20 +241,17 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function GetScript(string $sScriptName) : string
|
||||
{
|
||||
$sScriptName = \addcslashes($sScriptName, '"\\');
|
||||
$this->sendRequest('GETSCRIPT "'.$sScriptName.'"');
|
||||
$aResponse = $this->parseResponse();
|
||||
$this->validateResponse($aResponse);
|
||||
|
||||
$sScript = '';
|
||||
if (\count($aResponse))
|
||||
{
|
||||
if ('{' === $aResponse[0][0])
|
||||
{
|
||||
if (\count($aResponse)) {
|
||||
if ('{' === $aResponse[0][0]) {
|
||||
\array_shift($aResponse);
|
||||
}
|
||||
|
||||
if (\in_array(\substr($aResponse[\count($aResponse) - 1], 0, 2), array('OK', 'NO')))
|
||||
{
|
||||
if (\in_array(\substr($aResponse[\count($aResponse) - 1], 0, 2), array('OK', 'NO'))) {
|
||||
\array_pop($aResponse);
|
||||
}
|
||||
|
||||
|
|
@ -308,6 +268,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function PutScript(string $sScriptName, string $sScriptSource) : self
|
||||
{
|
||||
$sScriptName = \addcslashes($sScriptName, '"\\');
|
||||
$sScriptSource = \preg_replace('/\r?\n/', "\r\n", $sScriptSource);
|
||||
$this->sendRequest('PUTSCRIPT "'.$sScriptName.'" {'.\strlen($sScriptSource).'+}');
|
||||
$this->sendRequestWithCheck($sScriptSource);
|
||||
|
|
@ -336,6 +297,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function SetActiveScript(string $sScriptName) : self
|
||||
{
|
||||
$sScriptName = \addcslashes($sScriptName, '"\\');
|
||||
$this->sendRequestWithCheck('SETACTIVE "'.$sScriptName.'"');
|
||||
|
||||
return $this;
|
||||
|
|
@ -348,6 +310,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function DeleteScript(string $sScriptName) : self
|
||||
{
|
||||
$sScriptName = \addcslashes($sScriptName, '"\\');
|
||||
$this->sendRequestWithCheck('DELETESCRIPT "'.$sScriptName.'"');
|
||||
|
||||
return $this;
|
||||
|
|
@ -360,68 +323,22 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function RenameScript(string $sOldName, string $sNewName) : self
|
||||
{
|
||||
$sOldName = \addcslashes($sOldName, '"\\');
|
||||
$sNewName = \addcslashes($sNewName, '"\\');
|
||||
$this->sendRequestWithCheck('RENAMESCRIPT "'.$sOldName.'" "'.$sNewName.'"');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Sieve\Exceptions\*
|
||||
*/
|
||||
public function GetActiveScriptName() : string
|
||||
{
|
||||
$aList = $this->ListScripts();
|
||||
foreach ($aList as $sName => $bIsActive)
|
||||
{
|
||||
if ($bIsActive)
|
||||
{
|
||||
return $sName;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Sieve\Exceptions\*
|
||||
*/
|
||||
public function IsActiveScript(string $sScriptName) : bool
|
||||
{
|
||||
return $sScriptName === $this->GetActiveScriptName();
|
||||
}
|
||||
|
||||
private function parseLine(string $sLine) : ?array
|
||||
{
|
||||
if (false === $sLine || null === $sLine || \in_array(\substr($sLine, 0, 2), array('OK', 'NO')))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$iStart = -1;
|
||||
$iIndex = 0;
|
||||
$aResult = array();
|
||||
|
||||
for ($iPos = 0; $iPos < \strlen($sLine); $iPos++)
|
||||
{
|
||||
if ('"' === $sLine[$iPos] && '\\' !== $sLine[$iPos])
|
||||
{
|
||||
if (-1 === $iStart)
|
||||
{
|
||||
$iStart = $iPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResult[$iIndex++] = \substr($sLine, $iStart + 1, $iPos - $iStart - 1);
|
||||
$iStart = -1;
|
||||
}
|
||||
if (!\in_array(\substr($sLine, 0, 2), array('OK', 'NO'))) {
|
||||
$aResult = array();
|
||||
if (\preg_match_all('/(?:(?:"((?:\\\\"|[^"])*)"))/', $sLine, $aResult)) {
|
||||
return \array_map('stripcslashes', $aResult[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return isset($aResult[0]) ? $aResult : null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -431,21 +348,16 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
private function parseStartupResponse(array $aResponse) : void
|
||||
{
|
||||
foreach ($aResponse as $sLine)
|
||||
{
|
||||
foreach ($aResponse as $sLine) {
|
||||
$aTokens = $this->parseLine($sLine);
|
||||
|
||||
if (false === $aTokens || !isset($aTokens[0]) ||
|
||||
\in_array(\substr($sLine, 0, 2), array('OK', 'NO')))
|
||||
{
|
||||
if (empty($aTokens[0]) || \in_array(\substr($sLine, 0, 2), array('OK', 'NO'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sToken = \strtoupper($aTokens[0]);
|
||||
$this->aCapa[$sToken] = isset($aTokens[1]) ? $aTokens[1] : '';
|
||||
|
||||
if (isset($aTokens[1]))
|
||||
{
|
||||
if (isset($aTokens[1])) {
|
||||
switch ($sToken) {
|
||||
case 'SASL':
|
||||
$this->aAuth = \explode(' ', \strtoupper($aTokens[1]));
|
||||
|
|
@ -465,11 +377,8 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
private function sendRequest(string $sRequest) : void
|
||||
{
|
||||
if (!\strlen(\trim($sRequest)))
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \InvalidArgumentException,
|
||||
\LOG_ERR, true);
|
||||
if (!\strlen(\trim($sRequest))) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
||||
}
|
||||
|
||||
$this->IsConnected(true);
|
||||
|
|
@ -485,76 +394,45 @@ class ManageSieveClient extends \MailSo\Net\NetClient
|
|||
private function sendRequestWithCheck(string $sRequest) : void
|
||||
{
|
||||
$this->sendRequest($sRequest);
|
||||
$this->validateResponse($this->parseResponse());
|
||||
}
|
||||
|
||||
private function convertEndOfLine(string $sLine) : string
|
||||
{
|
||||
$sLine = \trim($sLine);
|
||||
if ('}' === \substr($sLine, -1))
|
||||
{
|
||||
$iPos = \strrpos($sLine, '{');
|
||||
if (false !== $iPos)
|
||||
{
|
||||
$sSunLine = \substr($sLine, $iPos + 1, -1);
|
||||
if (\is_numeric($sSunLine) && 0 < (int) $sSunLine)
|
||||
{
|
||||
$iLen = (int) $sSunLine;
|
||||
|
||||
$this->getNextBuffer($iLen, true);
|
||||
|
||||
if (\strlen($this->sResponseBuffer) === $iLen)
|
||||
{
|
||||
$sLine = \trim(\substr_replace($sLine, $this->sResponseBuffer, $iPos));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $sLine;
|
||||
$this->parseResponse();
|
||||
}
|
||||
|
||||
private function parseResponse() : array
|
||||
{
|
||||
$aResult = array();
|
||||
do
|
||||
{
|
||||
$this->getNextBuffer();
|
||||
|
||||
$sLine = $this->sResponseBuffer;
|
||||
if (false === $sLine)
|
||||
{
|
||||
while (true) {
|
||||
$sResponseBuffer = $this->getNextBuffer();
|
||||
if (null === $sResponseBuffer) {
|
||||
break;
|
||||
}
|
||||
else if (\in_array(\substr($sLine, 0, 2), array('OK', 'NO')))
|
||||
{
|
||||
$aResult[] = $this->convertEndOfLine($sLine);
|
||||
break;
|
||||
$bEnd = \in_array(\substr($sResponseBuffer, 0, 2), array('OK', 'NO'));
|
||||
// convertEndOfLine
|
||||
$sLine = \trim($sResponseBuffer);
|
||||
if ('}' === \substr($sLine, -1)) {
|
||||
$iPos = \strrpos($sLine, '{');
|
||||
if (false !== $iPos) {
|
||||
$iLen = \intval(\substr($sLine, $iPos + 1, -1));
|
||||
if (0 < $iLen) {
|
||||
$sResponseBuffer = $this->getNextBuffer($iLen);
|
||||
if (\strlen($sResponseBuffer) === $iLen) {
|
||||
$sLine = \trim(\substr_replace($sLine, $sResponseBuffer, $iPos));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResult[] = $this->convertEndOfLine($sLine);
|
||||
$aResult[] = $sLine;
|
||||
if ($bEnd) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (true);
|
||||
|
||||
if (!$aResult || 'OK' !== \substr($aResult[\array_key_last($aResult)], 0, 2)) {
|
||||
$this->writeLogException(new \MailSo\Sieve\Exceptions\NegativeResponseException($aResult), \LOG_WARNING);
|
||||
}
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \MailSo\Sieve\Exceptions\NegativeResponseException
|
||||
*/
|
||||
private function validateResponse(array $aResponse)
|
||||
{
|
||||
if (!$aResponse ||
|
||||
'OK' !== \substr($aResponse[\count($aResponse) - 1], 0, 2))
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \MailSo\Sieve\Exceptions\NegativeResponseException($aResponse),
|
||||
\LOG_WARNING, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function getLogName() : string
|
||||
{
|
||||
return 'SIEVE';
|
||||
|
|
@ -18,10 +18,7 @@ namespace MailSo\Smtp\Exceptions;
|
|||
*/
|
||||
class ResponseException extends \MailSo\RuntimeException
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aResponses;
|
||||
private array $aResponses;
|
||||
|
||||
public function __construct(array $aResponses = array(), string $sMessage = '', int $iCode = 0, ?\Throwable $oPrevious = null)
|
||||
{
|
||||
|
|
@ -37,7 +34,7 @@ class ResponseException extends \MailSo\RuntimeException
|
|||
|
||||
public function GetLastResponse() : ?\MailSo\Smtp\Response
|
||||
{
|
||||
$iCnt = count($this->aResponses);
|
||||
$iCnt = \count($this->aResponses);
|
||||
return $iCnt ? $this->aResponses[$iCnt - 1] : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
67
snappymail/v/0.0.0/app/libraries/MailSo/Smtp/Settings.php
Normal file
67
snappymail/v/0.0.0/app/libraries/MailSo/Smtp/Settings.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of MailSo.
|
||||
*
|
||||
* (c) 2022 DJMaze
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace MailSo\Smtp;
|
||||
|
||||
/**
|
||||
* @category MailSo
|
||||
* @package Net
|
||||
*/
|
||||
class Settings extends \MailSo\Net\ConnectSettings
|
||||
{
|
||||
public int
|
||||
$port = 25,
|
||||
$timeout = 60;
|
||||
|
||||
public bool
|
||||
$setSender = false,
|
||||
$usePhpMail = false,
|
||||
// https://github.com/the-djmaze/snappymail/issues/1038
|
||||
$authPlainLine = false,
|
||||
$viewErrors = false;
|
||||
|
||||
public string $Ehlo;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$oConfig = \RainLoop\API::Config();
|
||||
$this->viewErrors = !!$oConfig->Get('labs', 'smtp_show_server_errors', false);
|
||||
}
|
||||
|
||||
public static function fromArray(array $aSettings) : self
|
||||
{
|
||||
$object = parent::fromArray($aSettings);
|
||||
$object->useAuth = !empty($aSettings['useAuth']);
|
||||
$object->setSender = !empty($aSettings['setSender']);
|
||||
$object->usePhpMail = !empty($aSettings['usePhpMail']);
|
||||
$object->authPlainLine = !empty($aSettings['authPlainLine']);
|
||||
// $object->viewErrors = !empty($aSettings['viewErrors']);
|
||||
return $object;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return \array_merge(
|
||||
parent::jsonSerialize(),
|
||||
[
|
||||
// '@Object' => 'Object/SmtpSettings',
|
||||
'useAuth' => $this->useAuth,
|
||||
'setSender' => $this->setSender,
|
||||
'usePhpMail' => $this->usePhpMail,
|
||||
'authPlainLine' => $this->authPlainLine
|
||||
// 'viewErrors' => $this->viewErrors
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -19,78 +19,49 @@ use MailSo\Net\Enumerations\ConnectionSecurityType;
|
|||
*/
|
||||
class SmtpClient extends \MailSo\Net\NetClient
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sEhlo = '';
|
||||
private bool $bIsLoggined = false;
|
||||
|
||||
private string $sEhlo = '';
|
||||
|
||||
private bool $bRcpt = false;
|
||||
|
||||
private bool $bMail = false;
|
||||
|
||||
private array $aAuthTypes = array();
|
||||
|
||||
private array $aCapa = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
* RFC 1870
|
||||
*/
|
||||
private $bRcpt = false;
|
||||
private int $iSizeCapaValue = 0;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bMail = false;
|
||||
private array $aResults = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bData = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aAuthTypes = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aCapa = array();
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iSizeCapaValue = 0;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aResults = array();
|
||||
|
||||
public function IsSupported(string $sCapa) : bool
|
||||
public function hasCapability(string $sCapa) : bool
|
||||
{
|
||||
return \in_array(\strtoupper($sCapa), $this->aCapa);
|
||||
}
|
||||
|
||||
public function IsAuthSupported(string $sAuth) : bool
|
||||
public function maxSize() : int
|
||||
{
|
||||
return \in_array(\strtoupper($sAuth), $this->aAuthTypes);
|
||||
}
|
||||
|
||||
public function HasSupportedAuth() : bool
|
||||
{
|
||||
return $this->IsAuthSupported('PLAIN') || $this->IsAuthSupported('LOGIN');
|
||||
return $this->iSizeCapaValue;
|
||||
}
|
||||
|
||||
public static function EhloHelper() : string
|
||||
{
|
||||
$sEhloHost = empty($_SERVER['SERVER_NAME']) ? '' : \trim($_SERVER['SERVER_NAME']);
|
||||
if (empty($sEhloHost))
|
||||
{
|
||||
if (empty($sEhloHost)) {
|
||||
$sEhloHost = empty($_SERVER['HTTP_HOST']) ? '' : \trim($_SERVER['HTTP_HOST']);
|
||||
}
|
||||
|
||||
if (empty($sEhloHost))
|
||||
{
|
||||
if (empty($sEhloHost)) {
|
||||
$sEhloHost = \function_exists('gethostname') ? \gethostname() : 'localhost';
|
||||
}
|
||||
|
||||
$sEhloHost = \trim(\preg_replace('/:\d+$/', '', \trim($sEhloHost)));
|
||||
|
||||
if (\preg_match('/^\d+\.\d+\.\d+\.\d+$/', $sEhloHost))
|
||||
{
|
||||
if (\preg_match('/^\d+\.\d+\.\d+\.\d+$/', $sEhloHost)) {
|
||||
$sEhloHost = '['.$sEhloHost.']';
|
||||
}
|
||||
|
||||
|
|
@ -103,127 +74,140 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Smtp\Exceptions\*
|
||||
*/
|
||||
public function Connect(\MailSo\Net\ConnectSettings $oSettings, string $sEhloHost = '[127.0.0.1]') : void
|
||||
public function Connect(\MailSo\Net\ConnectSettings $oSettings) : void
|
||||
{
|
||||
parent::Connect($oSettings);
|
||||
|
||||
$this->validateResponse(220);
|
||||
|
||||
$this->ehloOrHelo($sEhloHost);
|
||||
$this->sEhlo = $sEhloHost;
|
||||
$this->ehloOrHelo($oSettings->Ehlo);
|
||||
$this->sEhlo = $oSettings->Ehlo;
|
||||
|
||||
if (ConnectionSecurityType::STARTTLS === $this->iSecurityType
|
||||
|| (ConnectionSecurityType::AUTO_DETECT === $this->iSecurityType && $this->IsSupported('STARTTLS'))) {
|
||||
if (ConnectionSecurityType::STARTTLS === $this->Settings->type
|
||||
|| (ConnectionSecurityType::AUTO_DETECT === $this->Settings->type && $this->hasCapability('STARTTLS'))) {
|
||||
$this->StartTLS();
|
||||
}
|
||||
}
|
||||
|
||||
private function StartTLS() : void
|
||||
{
|
||||
if ($this->IsSupported('STARTTLS')) {
|
||||
if ($this->hasCapability('STARTTLS')) {
|
||||
$this->sendRequestWithCheck('STARTTLS', 220);
|
||||
$this->EnableCrypto();
|
||||
$this->ehloOrHelo($this->sEhlo);
|
||||
} else {
|
||||
$this->writeLogException(
|
||||
new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'),
|
||||
\LOG_ERR, true);
|
||||
\LOG_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
public function supportsAuthType(string $sasl_type) : bool
|
||||
{
|
||||
return \in_array(\strtoupper($sasl_type), $this->aAuthTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws \MailSo\RuntimeException
|
||||
* @throws \MailSo\Net\*
|
||||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Smtp\Exceptions\*
|
||||
*/
|
||||
public function Login(array $aCredentials) : self
|
||||
public function Login(Settings $oSettings) : self
|
||||
{
|
||||
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($aCredentials['Login']));
|
||||
$sPassword = $aCredentials['Password'];
|
||||
if ($this->bIsLoggined) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($oSettings->Login));
|
||||
$sPassword = $oSettings->Password;
|
||||
|
||||
$type = '';
|
||||
// https://github.com/the-djmaze/snappymail/pull/423
|
||||
// $aCredentials['SASLMechanisms'][] = 'LOGIN';
|
||||
\array_unshift($aCredentials['SASLMechanisms'], 'LOGIN');
|
||||
foreach ($aCredentials['SASLMechanisms'] as $sasl_type) {
|
||||
if ($this->IsAuthSupported($sasl_type) && \SnappyMail\SASL::isSupported($sasl_type)) {
|
||||
foreach ($oSettings->SASLMechanisms as $sasl_type) {
|
||||
if (\in_array(\strtoupper($sasl_type), $this->aAuthTypes) && \SnappyMail\SASL::isSupported($sasl_type)) {
|
||||
$type = $sasl_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$type) {
|
||||
if (!$this->Encrypted() && $this->IsSupported('STARTTLS')) {
|
||||
if (!$this->Encrypted() && $this->hasCapability('STARTTLS')) {
|
||||
$this->StartTLS();
|
||||
return $this->Login($aCredentials);
|
||||
return $this->Login($oSettings);
|
||||
}
|
||||
\trigger_error("SMTP {$this->GetConnectedHost()} no supported AUTH options. Disable login");
|
||||
$this->writeLogException(
|
||||
new \MailSo\Smtp\Exceptions\LoginBadMethodException,
|
||||
\LOG_NOTICE, true);
|
||||
$this->writeLogException(new \MailSo\Smtp\Exceptions\LoginBadMethodException);
|
||||
}
|
||||
|
||||
$SASL = \SnappyMail\SASL::factory($type);
|
||||
$SASL->base64 = true;
|
||||
|
||||
// Start authentication
|
||||
try
|
||||
{
|
||||
$sResult = $this->sendRequestWithCheck('AUTH', 334, $type);
|
||||
}
|
||||
catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException)
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \MailSo\Smtp\Exceptions\LoginBadMethodException(
|
||||
$oException->GetResponses(), $oException->getMessage(), 0, $oException),
|
||||
\LOG_NOTICE, true);
|
||||
}
|
||||
if ($this->Settings->authPlainLine && $SASL instanceof \SnappyMail\SASL\Plain) {
|
||||
// https://github.com/the-djmaze/snappymail/issues/1038
|
||||
try
|
||||
{
|
||||
$sResult = $this->sendRequestWithCheck('AUTH', 235, 'PLAIN ' . $SASL->authenticate($sLogin, $sPassword));
|
||||
}
|
||||
catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException)
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \MailSo\Smtp\Exceptions\LoginBadCredentialsException($oException->GetResponses(), $oException->getMessage(), 0, $oException)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Start authentication
|
||||
try
|
||||
{
|
||||
$sResult = $this->sendRequestWithCheck('AUTH', 334, $type);
|
||||
}
|
||||
catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException)
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \MailSo\Smtp\Exceptions\LoginBadMethodException($oException->GetResponses(), $oException->getMessage(), 0, $oException)
|
||||
);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (0 === \strpos($type, 'SCRAM-')) {
|
||||
// RFC 5802
|
||||
$sResult = $this->sendRequestWithCheck($SASL->authenticate($sLogin, $sPassword, $sResult), 234, '');
|
||||
$sChallenge = $SASL->challenge($sResult);
|
||||
$this->oLogger && $this->oLogger->AddSecret($sChallenge);
|
||||
$SASL->verify($this->sendRequestWithCheck($sChallenge, 235, '', true));
|
||||
} else switch ($type) {
|
||||
// RFC 4616
|
||||
case 'PLAIN':
|
||||
case 'XOAUTH2':
|
||||
case 'OAUTHBEARER':
|
||||
$sAuth = $SASL->authenticate($sLogin, $sPassword);
|
||||
$this->oLogger && $this->oLogger->AddSecret($sAuth);
|
||||
$this->sendRequestWithCheck($sAuth, 235, '', true);
|
||||
break;
|
||||
try
|
||||
{
|
||||
if (\str_starts_with($type, 'SCRAM-')) {
|
||||
// RFC 5802
|
||||
$sResult = $this->sendRequestWithCheck($SASL->authenticate($sLogin, $sPassword, $sResult), 234, '');
|
||||
$sChallenge = $SASL->challenge($sResult);
|
||||
$this->logMask($sChallenge);
|
||||
$SASL->verify($this->sendRequestWithCheck($sChallenge, 235, '', true));
|
||||
} else switch ($type) {
|
||||
// RFC 4616
|
||||
case 'PLAIN':
|
||||
case 'XOAUTH2':
|
||||
case 'OAUTHBEARER':
|
||||
$sAuth = $SASL->authenticate($sLogin, $sPassword);
|
||||
$this->logMask($sAuth);
|
||||
$this->sendRequestWithCheck($sAuth, 235, '', true);
|
||||
break;
|
||||
|
||||
case 'LOGIN':
|
||||
$sResult = $this->sendRequestWithCheck($SASL->authenticate($sLogin, $sPassword, $sResult), 334, '');
|
||||
$sPassword = $SASL->challenge($sResult);
|
||||
$this->oLogger && $this->oLogger->AddSecret($sPassword);
|
||||
$this->sendRequestWithCheck($sPassword, 235, '', true);
|
||||
break;
|
||||
case 'LOGIN':
|
||||
$sResult = $this->sendRequestWithCheck($SASL->authenticate($sLogin, $sPassword, $sResult), 334, '');
|
||||
$sPassword = $SASL->challenge($sResult);
|
||||
$this->logMask($sPassword);
|
||||
$this->sendRequestWithCheck($sPassword, 235, '', true);
|
||||
break;
|
||||
|
||||
// RFC 2195
|
||||
case 'CRAM-MD5':
|
||||
if (empty($sResult)) {
|
||||
$this->writeLogException(
|
||||
new \MailSo\Smtp\Exceptions\NegativeResponseException,
|
||||
\LOG_NOTICE, true
|
||||
);
|
||||
// RFC 2195
|
||||
case 'CRAM-MD5':
|
||||
if (empty($sResult)) {
|
||||
$this->writeLogException(new \MailSo\Smtp\Exceptions\NegativeResponseException);
|
||||
}
|
||||
$this->sendRequestWithCheck($SASL->authenticate($sLogin, $sPassword, $sResult), 235, '', true);
|
||||
break;
|
||||
}
|
||||
$this->sendRequestWithCheck($SASL->authenticate($sLogin, $sPassword, $sResult), 235, '', true);
|
||||
break;
|
||||
}
|
||||
catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException)
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \MailSo\Smtp\Exceptions\LoginBadCredentialsException($oException->GetResponses(), $oException->getMessage(), 0, $oException)
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException)
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \MailSo\Smtp\Exceptions\LoginBadCredentialsException(
|
||||
$oException->GetResponses(), $oException->getMessage(), 0, $oException),
|
||||
\LOG_NOTICE, true);
|
||||
}
|
||||
|
||||
$this->bIsLoggined = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
@ -233,28 +217,35 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
* @throws \MailSo\Net\Exceptions\*
|
||||
* @throws \MailSo\Smtp\Exceptions\*
|
||||
*/
|
||||
public function MailFrom(string $sFrom, string $sSizeIfSupported = '', bool $bDsn = false) : self
|
||||
public function MailFrom(string $sFrom, int $iSizeIfSupported = 0, bool $bDsn = false, bool $bRequireTLS = false) : self
|
||||
{
|
||||
$sFrom = \MailSo\Base\Utils::IdnToAscii(
|
||||
\MailSo\Base\Utils::Trim($sFrom), true);
|
||||
$sFrom = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sFrom), true);
|
||||
|
||||
$sCmd = 'FROM:<'.$sFrom.'>';
|
||||
$sCmd = "FROM:<{$sFrom}>";
|
||||
|
||||
if (\strlen($sSizeIfSupported) && \is_numeric($sSizeIfSupported) && $this->IsSupported('SIZE'))
|
||||
{
|
||||
$sCmd .= ' SIZE='.$sSizeIfSupported;
|
||||
if (0 < $iSizeIfSupported && $this->hasCapability('SIZE')) {
|
||||
$sCmd .= ' SIZE='.$iSizeIfSupported;
|
||||
}
|
||||
|
||||
if ($bDsn && $this->IsSupported('DSN'))
|
||||
{
|
||||
// RFC 3461
|
||||
if ($bDsn && $this->hasCapability('DSN')) {
|
||||
$sCmd .= ' RET=HDRS';
|
||||
}
|
||||
|
||||
// RFC 6152
|
||||
if ($this->hasCapability('8BITMIME')) {
|
||||
// $sCmd .= ' BODY=8BITMIME';
|
||||
}
|
||||
|
||||
// RFC 8689
|
||||
if ($bRequireTLS && $this->hasCapability('REQUIRETLS')) {
|
||||
$sCmd .= ' REQUIRETLS';
|
||||
}
|
||||
|
||||
$this->sendRequestWithCheck('MAIL', 250, $sCmd);
|
||||
|
||||
$this->bMail = true;
|
||||
$this->bRcpt = false;
|
||||
$this->bData = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
@ -266,20 +257,15 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function Rcpt(string $sTo, bool $bDsn = false) : self
|
||||
{
|
||||
if (!$this->bMail)
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \MailSo\RuntimeException('No sender reverse path has been supplied'),
|
||||
\LOG_ERR, true);
|
||||
if (!$this->bMail) {
|
||||
$this->writeLogException(new \MailSo\RuntimeException('No sender reverse path has been supplied'), \LOG_ERR);
|
||||
}
|
||||
|
||||
$sTo = \MailSo\Base\Utils::IdnToAscii(
|
||||
\MailSo\Base\Utils::Trim($sTo), true);
|
||||
$sTo = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sTo), true);
|
||||
|
||||
$sCmd = 'TO:<'.$sTo.'>';
|
||||
|
||||
if ($bDsn && $this->IsSupported('DSN'))
|
||||
{
|
||||
if ($bDsn && $this->hasCapability('DSN')) {
|
||||
$sCmd .= ' NOTIFY=SUCCESS,FAILURE';
|
||||
}
|
||||
|
||||
|
|
@ -310,8 +296,7 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function Data(string $sData) : self
|
||||
{
|
||||
if (!\strlen(\trim($sData)))
|
||||
{
|
||||
if (!\strlen(\trim($sData))) {
|
||||
throw new \InvalidArgumentException;
|
||||
}
|
||||
|
||||
|
|
@ -333,16 +318,12 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function DataWithStream($rDataStream) : self
|
||||
{
|
||||
if (!\is_resource($rDataStream))
|
||||
{
|
||||
if (!\is_resource($rDataStream)) {
|
||||
throw new \InvalidArgumentException;
|
||||
}
|
||||
|
||||
if (!$this->bRcpt)
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \MailSo\RuntimeException('No recipient forward path has been supplied'),
|
||||
\LOG_ERR, true);
|
||||
if (!$this->bRcpt) {
|
||||
$this->writeLogException(new \MailSo\RuntimeException('No recipient forward path has been supplied'), \LOG_ERR);
|
||||
}
|
||||
|
||||
$this->sendRequestWithCheck('DATA', 354);
|
||||
|
|
@ -351,37 +332,25 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
|
||||
$this->bRunningCallback = true;
|
||||
|
||||
while (!\feof($rDataStream))
|
||||
{
|
||||
while (!\feof($rDataStream)) {
|
||||
$sBuffer = \fgets($rDataStream);
|
||||
if (false !== $sBuffer)
|
||||
{
|
||||
if (0 === \strpos($sBuffer, '.'))
|
||||
{
|
||||
$sBuffer = '.'.$sBuffer;
|
||||
if (false === $sBuffer) {
|
||||
if (!\feof($rDataStream)) {
|
||||
$this->writeLogException(new \MailSo\RuntimeException('Cannot read input resource'), \LOG_ERR);
|
||||
}
|
||||
|
||||
$this->sendRaw(\rtrim($sBuffer, "\r\n"), false);
|
||||
|
||||
\MailSo\Base\Utils::ResetTimeLimit();
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
else if (!\feof($rDataStream))
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \MailSo\RuntimeException('Cannot read input resource'),
|
||||
\LOG_ERR, true);
|
||||
if (\str_starts_with($sBuffer, '.')) {
|
||||
$sBuffer = '.' . $sBuffer;
|
||||
}
|
||||
|
||||
break;
|
||||
$this->sendRaw(\rtrim($sBuffer, "\r\n"), false);
|
||||
\MailSo\Base\Utils::ResetTimeLimit();
|
||||
}
|
||||
|
||||
$this->sendRequestWithCheck('.', 250);
|
||||
|
||||
$this->bRunningCallback = false;
|
||||
|
||||
$this->bData = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -396,7 +365,6 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
|
||||
$this->bMail = false;
|
||||
$this->bRcpt = false;
|
||||
$this->bData = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
@ -408,11 +376,8 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function Vrfy(string $sUser) : self
|
||||
{
|
||||
$sUser = \MailSo\Base\Utils::IdnToAscii(
|
||||
\MailSo\Base\Utils::Trim($sUser));
|
||||
|
||||
$this->sendRequestWithCheck('VRFY', array(250, 251, 252), $sUser);
|
||||
|
||||
$this->sendRequestWithCheck('VRFY', array(250, 251, 252),
|
||||
\MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sUser)));
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -435,14 +400,11 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
public function Logout() : void
|
||||
{
|
||||
if ($this->IsConnected())
|
||||
{
|
||||
if ($this->IsConnected()) {
|
||||
$this->sendRequestWithCheck('QUIT', 221);
|
||||
}
|
||||
|
||||
$this->bMail = false;
|
||||
$this->bRcpt = false;
|
||||
$this->bData = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -452,19 +414,16 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
private function sendRequest(string $sCommand, string $sAddToCommand = '', bool $bSecureLog = false) : void
|
||||
{
|
||||
if (!\strlen(\trim($sCommand)))
|
||||
{
|
||||
$this->writeLogException(
|
||||
new \InvalidArgumentException,
|
||||
\LOG_ERR, true);
|
||||
$sCommand = \trim($sCommand);
|
||||
if (!\strlen($sCommand)) {
|
||||
$this->writeLogException(new \InvalidArgumentException, \LOG_ERR);
|
||||
}
|
||||
|
||||
$this->IsConnected(true);
|
||||
|
||||
$sCommand = \trim($sCommand);
|
||||
$sRealCommand = $sCommand.(0 === \strlen($sAddToCommand) ? '' : ' '.$sAddToCommand);
|
||||
$sRealCommand = $sCommand . (\strlen($sAddToCommand) ? ' '.$sAddToCommand : '');
|
||||
|
||||
$sFakeCommand = ($bSecureLog) ? '********' : '';
|
||||
$sFakeCommand = $bSecureLog ? '********' : '';
|
||||
|
||||
$this->sendRaw($sRealCommand, true, $sFakeCommand);
|
||||
}
|
||||
|
|
@ -509,33 +468,32 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
private function ehlo(string $sHost) : void
|
||||
{
|
||||
$this->sendRequestWithCheck('EHLO', 250, $sHost);
|
||||
|
||||
foreach ($this->aResults as $sLine)
|
||||
{
|
||||
/*
|
||||
250-PIPELINING\r\n
|
||||
250-SIZE 256000000\r\n
|
||||
250-ETRN\r\n
|
||||
250-STARTTLS\r\n
|
||||
250-ENHANCEDSTATUSCODES\r\n
|
||||
250-8BITMIME\r\n
|
||||
250-DSN\r\n
|
||||
250 SMTPUTF8\r\n
|
||||
*/
|
||||
foreach ($this->aResults as $sLine) {
|
||||
$aMatch = array();
|
||||
if (\preg_match('/[\d]+[ \-](.+)$/', $sLine, $aMatch) && isset($aMatch[1]) && \strlen($aMatch[1]))
|
||||
{
|
||||
$sLine = \trim($aMatch[1]);
|
||||
$aLine = \preg_split('/[ =]/', $sLine, 2);
|
||||
if (!empty($aLine[0]))
|
||||
{
|
||||
if (\preg_match('/[\d]+[ \-](.+)$/', $sLine, $aMatch) && isset($aMatch[1]) && \strlen($aMatch[1])) {
|
||||
$aLine = \preg_split('/[ =]/', \trim($aMatch[1]), 2);
|
||||
if (!empty($aLine[0])) {
|
||||
$sCapa = \strtoupper($aLine[0]);
|
||||
if (('AUTH' === $sCapa || 'SIZE' === $sCapa) && !empty($aLine[1]))
|
||||
{
|
||||
if (!empty($aLine[1]) && ('AUTH' === $sCapa || 'SIZE' === $sCapa)) {
|
||||
$sSubLine = \trim(\strtoupper($aLine[1]));
|
||||
if (\strlen($sSubLine))
|
||||
{
|
||||
if ('AUTH' === $sCapa)
|
||||
{
|
||||
if (\strlen($sSubLine)) {
|
||||
if ('AUTH' === $sCapa) {
|
||||
$this->aAuthTypes = \explode(' ', $sSubLine);
|
||||
}
|
||||
else if ('SIZE' === $sCapa && \is_numeric($sSubLine))
|
||||
{
|
||||
} else if ('SIZE' === $sCapa && \is_numeric($sSubLine)) {
|
||||
$this->iSizeCapaValue = (int) $sSubLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->aCapa[] = $sCapa;
|
||||
}
|
||||
}
|
||||
|
|
@ -560,43 +518,34 @@ class SmtpClient extends \MailSo\Net\NetClient
|
|||
*/
|
||||
private function validateResponse($mExpectCode, string $sErrorPrefix = '') : void
|
||||
{
|
||||
if (!\is_array($mExpectCode))
|
||||
{
|
||||
$mExpectCode = array((int) $mExpectCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
$mExpectCode = \array_map('intval', $mExpectCode);
|
||||
}
|
||||
$mExpectCode = \is_array($mExpectCode)
|
||||
? \array_map('intval', $mExpectCode)
|
||||
: array((int) $mExpectCode);
|
||||
|
||||
$aParts = array('', '', '');
|
||||
$this->aResults = array();
|
||||
do
|
||||
{
|
||||
$this->getNextBuffer();
|
||||
$aParts = \preg_split('/([\s\-]+)/', $this->sResponseBuffer, 2, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$sResponse = $this->getNextBuffer();
|
||||
$aParts = \preg_split('/([\s\-]+)/', $sResponse, 2, PREG_SPLIT_DELIM_CAPTURE);
|
||||
|
||||
if (3 === \count($aParts) && \is_numeric($aParts[0]))
|
||||
{
|
||||
if ('-' !== \substr($aParts[1], 0, 1) && !\in_array((int) $aParts[0], $mExpectCode))
|
||||
{
|
||||
if (3 === \count($aParts) && \is_numeric($aParts[0])) {
|
||||
if ('-' !== \substr($aParts[1], 0, 1) && !\in_array((int) $aParts[0], $mExpectCode)) {
|
||||
$this->writeLogException(
|
||||
new Exceptions\NegativeResponseException($this->aResults,
|
||||
('' === $sErrorPrefix ? '' : $sErrorPrefix.': ').\trim(
|
||||
(\count($this->aResults) ? \implode("\r\n", $this->aResults)."\r\n" : '').
|
||||
$this->sResponseBuffer)), \LOG_ERR, true);
|
||||
$sResponse)), \LOG_ERR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$this->writeLogException(
|
||||
new Exceptions\ResponseException($this->aResults,
|
||||
('' === $sErrorPrefix ? '' : $sErrorPrefix.': ').\trim(
|
||||
(\count($this->aResults) ? \implode("\r\n", $this->aResults)."\r\n" : '').
|
||||
$this->sResponseBuffer)), \LOG_ERR, true);
|
||||
$sResponse)), \LOG_ERR);
|
||||
}
|
||||
|
||||
$this->aResults[] = $this->sResponseBuffer;
|
||||
$this->aResults[] = $sResponse;
|
||||
}
|
||||
while ('-' === \substr($aParts[1], 0, 1));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace RainLoop;
|
||||
|
||||
use RainLoop\Enumerations\UploadError;
|
||||
|
||||
class Actions
|
||||
{
|
||||
use Actions\Admin;
|
||||
|
|
@ -14,6 +12,8 @@ class Actions
|
|||
use Actions\Localization;
|
||||
use Actions\Themes;
|
||||
|
||||
use \MailSo\Log\Inherit;
|
||||
|
||||
const AUTH_MAILTO_TOKEN_KEY = 'smmailtoauth';
|
||||
|
||||
/**
|
||||
|
|
@ -26,16 +26,17 @@ class Actions
|
|||
|
||||
/**
|
||||
* This session cookie contains a \RainLoop\Model\Account
|
||||
* Value is Base64 EncryptToJSON
|
||||
*/
|
||||
const AUTH_SPEC_TOKEN_KEY = 'smaccount';
|
||||
|
||||
/**
|
||||
* This session cookie optionally contains a \RainLoop\Model\AdditionalAccount
|
||||
* Value is Base64 EncryptToJSON
|
||||
*/
|
||||
const AUTH_ADDITIONAL_TOKEN_KEY = 'smadditional';
|
||||
|
||||
const AUTH_SPEC_LOGOUT_TOKEN_KEY = 'smspeclogout';
|
||||
const AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY = 'smspeclogoutcmk';
|
||||
const APP_DUMMY = '********';
|
||||
|
||||
/**
|
||||
* @var \MailSo\Base\Http
|
||||
|
|
@ -57,11 +58,6 @@ class Actions
|
|||
*/
|
||||
protected $oPlugins = null;
|
||||
|
||||
/**
|
||||
* @var \MailSo\Log\Logger
|
||||
*/
|
||||
protected $oLogger = null;
|
||||
|
||||
/**
|
||||
* @var \MailSo\Log\Logger
|
||||
*/
|
||||
|
|
@ -128,9 +124,12 @@ class Actions
|
|||
$sLogFileName = $this->oConfig->Get('logs', 'filename', '');
|
||||
if ('syslog' === $sLogFileName) {
|
||||
$oDriver = new \MailSo\Log\Drivers\Syslog();
|
||||
} else if ('stderr' === $sLogFileName) {
|
||||
$oDriver = new \MailSo\Log\Drivers\StderrStream();
|
||||
} else {
|
||||
$sLogFileFullPath = \APP_PRIVATE_DATA . 'logs/' . $this->compileLogFileName($sLogFileName);
|
||||
$oDriver = new \MailSo\Log\Drivers\File($sLogFileFullPath);
|
||||
$sLogFileFullPath = \trim($this->oConfig->Get('logs', 'path', '')) ?: \APP_PRIVATE_DATA . 'logs';
|
||||
\is_dir($sLogFileFullPath) || \mkdir($sLogFileFullPath, 0700, true);
|
||||
$oDriver = new \MailSo\Log\Drivers\File($sLogFileFullPath . '/' . $this->compileLogFileName($sLogFileName));
|
||||
}
|
||||
$this->oLogger->append($oDriver
|
||||
->SetTimeZone($this->oConfig->Get('logs', 'time_zone', 'UTC'))
|
||||
|
|
@ -138,14 +137,14 @@ class Actions
|
|||
|
||||
$oHttp = $this->Http();
|
||||
|
||||
$this->oLogger->Write(
|
||||
$this->logWrite(
|
||||
'[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) . \MailSo\Base\Http::GetServer('REQUEST_URI', '') . ']'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -243,22 +242,22 @@ class Actions
|
|||
public function BootEnd(): void
|
||||
{
|
||||
try {
|
||||
if ($this->MailClient()->IsLoggined()) {
|
||||
$this->MailClient()->Disconnect();
|
||||
if ($this->ImapClient()->IsLoggined()) {
|
||||
$this->ImapClient()->Disconnect();
|
||||
}
|
||||
} catch (\Throwable $oException) {
|
||||
unset($oException);
|
||||
}
|
||||
}
|
||||
|
||||
protected function compileLogParams(string $sLine, ?Model\Account $oAccount = null, bool $bUrlEncode = false, array $aAdditionalParams = array()): string
|
||||
protected function compileLogParams(string $sLine, ?Model\Account $oAccount = null, array $aAdditionalParams = array()): string
|
||||
{
|
||||
$aClear = array();
|
||||
|
||||
if (false !== \strpos($sLine, '{date:')) {
|
||||
$oConfig = $this->oConfig;
|
||||
$sLine = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) use ($oConfig, $bUrlEncode) {
|
||||
return Utils::UrlEncode((new \DateTime('now', new \DateTimeZone($oConfig->Get('logs', 'time_zone', 'UTC'))))->format($aMatch[1]), $bUrlEncode);
|
||||
$sLine = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) use ($oConfig) {
|
||||
return (new \DateTime('now', new \DateTimeZone($oConfig->Get('logs', 'time_zone', 'UTC'))))->format($aMatch[1]);
|
||||
}, $sLine);
|
||||
|
||||
$aClear['/\{date:([^}]*)\}/'] = 'date';
|
||||
|
|
@ -270,13 +269,13 @@ class Actions
|
|||
}
|
||||
|
||||
if ($oAccount) {
|
||||
$sLine = \str_replace('{imap:login}', Utils::UrlEncode($oAccount->IncLogin(), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{imap:host}', Utils::UrlEncode($oAccount->Domain()->IncHost(), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{imap:port}', Utils::UrlEncode($oAccount->Domain()->IncPort(), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{imap:login}', $oAccount->IncLogin(), $sLine);
|
||||
$sLine = \str_replace('{imap:host}', $oAccount->Domain()->IncHost(), $sLine);
|
||||
$sLine = \str_replace('{imap:port}', $oAccount->Domain()->IncPort(), $sLine);
|
||||
|
||||
$sLine = \str_replace('{smtp:login}', Utils::UrlEncode($oAccount->OutLogin(), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{smtp:host}', Utils::UrlEncode($oAccount->Domain()->OutHost(), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{smtp:port}', Utils::UrlEncode($oAccount->Domain()->OutPort(), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{smtp:login}', $oAccount->OutLogin(), $sLine);
|
||||
$sLine = \str_replace('{smtp:host}', $oAccount->Domain()->OutHost(), $sLine);
|
||||
$sLine = \str_replace('{smtp:port}', $oAccount->Domain()->OutPort(), $sLine);
|
||||
}
|
||||
|
||||
$aClear['/\{imap:([^}]*)\}/i'] = 'imap';
|
||||
|
|
@ -285,19 +284,19 @@ class Actions
|
|||
|
||||
if (false !== \strpos($sLine, '{request:')) {
|
||||
if (false !== \strpos($sLine, '{request:ip}')) {
|
||||
$sLine = \str_replace('{request:ip}', Utils::UrlEncode($this->Http()->GetClientIp(
|
||||
$this->oConfig->Get('labs', 'http_client_ip_check_proxy', false)), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{request:ip}',
|
||||
$this->Http()->GetClientIp($this->oConfig->Get('labs', 'http_client_ip_check_proxy', false)),
|
||||
$sLine);
|
||||
}
|
||||
|
||||
if (false !== \strpos($sLine, '{request:domain}')) {
|
||||
$sLine = \str_replace('{request:domain}',
|
||||
Utils::UrlEncode($this->Http()->GetHost(false, true, true), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{request:domain}', $this->Http()->GetHost(true, true), $sLine);
|
||||
}
|
||||
|
||||
if (false !== \strpos($sLine, '{request:domain-clear}')) {
|
||||
$sLine = \str_replace('{request:domain-clear}',
|
||||
Utils::UrlEncode(
|
||||
\MailSo\Base\Utils::GetClearDomainName($this->Http()->GetHost(false, true, true)), $bUrlEncode), $sLine);
|
||||
\MailSo\Base\Utils::GetClearDomainName($this->Http()->GetHost(true, true)),
|
||||
$sLine);
|
||||
}
|
||||
|
||||
$aClear['/\{request:([^}]*)\}/i'] = 'request';
|
||||
|
|
@ -306,15 +305,15 @@ class Actions
|
|||
if (false !== \strpos($sLine, '{user:')) {
|
||||
if (false !== \strpos($sLine, '{user:uid}')) {
|
||||
$sLine = \str_replace('{user:uid}',
|
||||
Utils::UrlEncode(\base_convert(\sprintf('%u',
|
||||
\crc32(Utils::GetConnectionToken())), 10, 32), $bUrlEncode),
|
||||
\base_convert(\sprintf('%u', \crc32(Utils::GetConnectionToken())), 10, 32),
|
||||
$sLine
|
||||
);
|
||||
}
|
||||
|
||||
if (false !== \strpos($sLine, '{user:ip}')) {
|
||||
$sLine = \str_replace('{user:ip}', Utils::UrlEncode($this->Http()->GetClientIp(
|
||||
$this->oConfig->Get('labs', 'http_client_ip_check_proxy', false)), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{user:ip}',
|
||||
$this->Http()->GetClientIp($this->oConfig->Get('labs', 'http_client_ip_check_proxy', false)),
|
||||
$sLine);
|
||||
}
|
||||
|
||||
if (\preg_match('/\{user:(email|login|domain)\}/i', $sLine)) {
|
||||
|
|
@ -325,14 +324,12 @@ class Actions
|
|||
if ($oAccount) {
|
||||
$sEmail = $oAccount->Email();
|
||||
|
||||
$sLine = \str_replace('{user:email}', Utils::UrlEncode($sEmail, $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{user:login}', Utils::UrlEncode(
|
||||
\MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{user:domain}', Utils::UrlEncode(
|
||||
\MailSo\Base\Utils::GetDomainFromEmail($sEmail), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{user:domain-clear}', Utils::UrlEncode(
|
||||
\MailSo\Base\Utils::GetClearDomainName(
|
||||
\MailSo\Base\Utils::GetDomainFromEmail($sEmail)), $bUrlEncode), $sLine);
|
||||
$sLine = \str_replace('{user:email}', $sEmail, $sLine);
|
||||
$sLine = \str_replace('{user:login}', \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $sLine);
|
||||
$sLine = \str_replace('{user:domain}', \MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sLine);
|
||||
$sLine = \str_replace('{user:domain-clear}',
|
||||
\MailSo\Base\Utils::GetClearDomainName(\MailSo\Base\Utils::GetDomainFromEmail($sEmail)),
|
||||
$sLine);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -403,6 +400,12 @@ class Actions
|
|||
return $this->oMailClient;
|
||||
}
|
||||
|
||||
public function ImapClient(): \MailSo\Imap\ImapClient
|
||||
{
|
||||
// $this->initMailClientConnection();
|
||||
return $this->MailClient()->ImapClient();
|
||||
}
|
||||
|
||||
// Stores data in AdditionalAccount else MainAccount
|
||||
public function LocalStorageProvider(): Providers\Storage
|
||||
{
|
||||
|
|
@ -460,13 +463,13 @@ class Actions
|
|||
return $this->oDomainProvider;
|
||||
}
|
||||
|
||||
public function AddressBookProvider(?Model\Account $oAccount = null, bool $bForceEnable = false): Providers\AddressBook
|
||||
public function AddressBookProvider(?Model\Account $oAccount = null): Providers\AddressBook
|
||||
{
|
||||
if (null === $this->oAddressBookProvider) {
|
||||
$oDriver = null;
|
||||
try {
|
||||
// if ($bForceEnable || $this->oConfig->Get('contacts', 'enable', false)) {
|
||||
if ($bForceEnable || $this->GetCapa(Enumerations\Capa::CONTACTS)) {
|
||||
// if ($this->oConfig->Get('contacts', 'enable', false)) {
|
||||
if ($this->GetCapa(Enumerations\Capa::CONTACTS)) {
|
||||
$oDriver = $this->fabrica('address-book', $oAccount);
|
||||
}
|
||||
if ($oAccount && $oDriver) {
|
||||
|
|
@ -474,8 +477,9 @@ class Actions
|
|||
$oDriver->setDAVClientConfig($this->getContactsSyncData($oAccount));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
\SnappyMail\LOG::error('AddressBook', $e->getMessage()."\n".$e->getTraceAsString());
|
||||
$oDriver = null;
|
||||
\SnappyMail\LOG::error('AddressBook', $e->getMessage());
|
||||
// $oDriver = new Providers\AddressBook\PdoAddressBook();
|
||||
}
|
||||
$this->oAddressBookProvider = new Providers\AddressBook($oDriver);
|
||||
$this->oAddressBookProvider->SetLogger($this->oLogger);
|
||||
|
|
@ -505,7 +509,10 @@ class Actions
|
|||
switch (true) {
|
||||
default:
|
||||
case $bForceFile:
|
||||
$oDriver = new \MailSo\Cache\Drivers\File(APP_PRIVATE_DATA . 'cache', $sKey);
|
||||
$oDriver = new \MailSo\Cache\Drivers\File(
|
||||
\trim($this->oConfig->Get('cache', 'path', '')) ?: APP_PRIVATE_DATA . 'cache',
|
||||
$sKey
|
||||
);
|
||||
break;
|
||||
|
||||
case ('APCU' === $sDriver) &&
|
||||
|
|
@ -550,11 +557,6 @@ class Actions
|
|||
return $this->oPlugins;
|
||||
}
|
||||
|
||||
public function Logger(): \MailSo\Log\Logger
|
||||
{
|
||||
return $this->oLogger;
|
||||
}
|
||||
|
||||
public function LoggerAuth(): \MailSo\Log\Logger
|
||||
{
|
||||
if (!$this->oLoggerAuth) {
|
||||
|
|
@ -562,13 +564,10 @@ class Actions
|
|||
if ($this->oConfig->Get('logs', 'auth_logging', false)) {
|
||||
// $this->oLoggerAuth->SetLevel(\LOG_WARNING);
|
||||
|
||||
$sAuthLogFileFullPath = \APP_PRIVATE_DATA . 'logs/' . $this->compileLogFileName(
|
||||
$this->oConfig->Get('logs', 'auth_logging_filename', ''));
|
||||
$sAuthLogFileFullPath = (\trim($this->oConfig->Get('logs', 'path', '') ?: \APP_PRIVATE_DATA . 'logs'))
|
||||
. '/' . $this->compileLogFileName($this->oConfig->Get('logs', 'auth_logging_filename', ''));
|
||||
$sLogFileDir = \dirname($sAuthLogFileFullPath);
|
||||
if (!\is_dir($sLogFileDir)) {
|
||||
\mkdir($sLogFileDir, 0755, true);
|
||||
}
|
||||
|
||||
\is_dir($sLogFileDir) || \mkdir($sLogFileDir, 0755, true);
|
||||
$this->oLoggerAuth->append(
|
||||
(new \MailSo\Log\Drivers\File($sAuthLogFileFullPath))
|
||||
->DisableTimePrefix()
|
||||
|
|
@ -584,13 +583,13 @@ class Actions
|
|||
{
|
||||
$sLine = $this->oConfig->Get('logs', 'auth_logging_format', '');
|
||||
if (!empty($sLine)) {
|
||||
$this->LoggerAuth()->Write($this->compileLogParams($sLine, $oAccount, false, $aAdditionalParams));
|
||||
$this->LoggerAuth()->Write($this->compileLogParams($sLine, $oAccount, $aAdditionalParams), \LOG_WARNING);
|
||||
}
|
||||
if (($this->oConfig->Get('logs', 'auth_logging', false) || $this->oConfig->Get('logs', 'auth_syslog', false))
|
||||
&& \openlog('snappymail', 0, \LOG_AUTHPRIV)) {
|
||||
\syslog(\LOG_ERR, $this->compileLogParams(
|
||||
$admin ? 'Admin Auth failed: ip={request:ip} user={user:login}' : 'Auth failed: ip={request:ip} user={imap:login}',
|
||||
$oAccount, false, $aAdditionalParams
|
||||
$oAccount, $aAdditionalParams
|
||||
));
|
||||
\closelog();
|
||||
}
|
||||
|
|
@ -603,60 +602,61 @@ class Actions
|
|||
|
||||
$aResult = array(
|
||||
'Auth' => false,
|
||||
'Title' => $oConfig->Get('webmail', 'title', 'SnappyMail Webmail'),
|
||||
'LoadingDescription' => $oConfig->Get('webmail', 'loading_description', 'SnappyMail'),
|
||||
'title' => $oConfig->Get('webmail', 'title', 'SnappyMail Webmail'),
|
||||
'loadingDescription' => $oConfig->Get('webmail', 'loading_description', 'SnappyMail'),
|
||||
'Plugins' => array(),
|
||||
'System' => \array_merge(
|
||||
array(
|
||||
'version' => APP_VERSION,
|
||||
'token' => $oConfig->Get('security', 'csrf_protection', false) ? Utils::GetCsrfToken() : '',
|
||||
'token' => Utils::GetCsrfToken(),
|
||||
'languages' => \SnappyMail\L10n::getLanguages(false),
|
||||
'webPath' => \RainLoop\Utils::WebPath(),
|
||||
'webVersionPath' => \RainLoop\Utils::WebVersionPath()
|
||||
), $bAdmin ? array(
|
||||
'adminHostUse' => '' !== $oConfig->Get('security', 'admin_panel_host', ''),
|
||||
'adminHost' => '' !== $oConfig->Get('security', 'admin_panel_host', ''),
|
||||
'adminPath' => $oConfig->Get('security', 'admin_panel_key', '') ?: 'admin',
|
||||
'adminAllowed' => (bool)$oConfig->Get('security', 'allow_admin_panel', true)
|
||||
) : array()
|
||||
),
|
||||
'AllowLanguagesOnLogin' => (bool) $oConfig->Get('login', 'allow_languages_on_login', true)
|
||||
'allowLanguagesOnLogin' => (bool) $oConfig->Get('login', 'allow_languages_on_login', true)
|
||||
);
|
||||
|
||||
$sLanguage = $oConfig->Get('webmail', 'language', 'en');
|
||||
$UserLanguageRaw = $this->detectUserLanguage($bAdmin);
|
||||
|
||||
if ($bAdmin) {
|
||||
// $this->AdminAppData($aResult);
|
||||
$aResult['Auth'] = $this->IsAdminLoggined(false);
|
||||
if ($aResult['Auth']) {
|
||||
$aResult['AdminLogin'] = (string)$oConfig->Get('security', 'admin_login', '');
|
||||
$aResult['AdminTOTP'] = (string)$oConfig->Get('security', 'admin_totp', '');
|
||||
$aResult['UseTokenProtection'] = (bool)$oConfig->Get('security', 'csrf_protection', true);
|
||||
$aResult['EnabledPlugins'] = (bool)$oConfig->Get('plugins', 'enable', false);
|
||||
$aResult['adminLogin'] = (string)$oConfig->Get('security', 'admin_login', '');
|
||||
$aResult['adminTOTP'] = (string)$oConfig->Get('security', 'admin_totp', '');
|
||||
$aResult['pluginsEnable'] = (bool)$oConfig->Get('plugins', 'enable', false);
|
||||
|
||||
$aResult['LoginDefaultDomain'] = $oConfig->Get('login', 'default_domain', '');
|
||||
$aResult['DetermineUserLanguage'] = (bool)$oConfig->Get('login', 'determine_user_language', true);
|
||||
$aResult['DetermineUserDomain'] = (bool)$oConfig->Get('login', 'determine_user_domain', false);
|
||||
$aResult['loginDefaultDomain'] = $oConfig->Get('login', 'default_domain', '');
|
||||
$aResult['determineUserLanguage'] = (bool)$oConfig->Get('login', 'determine_user_language', true);
|
||||
$aResult['determineUserDomain'] = (bool)$oConfig->Get('login', 'determine_user_domain', false);
|
||||
|
||||
$aResult['VerifySslCertificate'] = (bool)$oConfig->Get('ssl', 'verify_certificate', false);
|
||||
$aResult['AllowSelfSigned'] = (bool)$oConfig->Get('ssl', 'allow_self_signed', true);
|
||||
$aResult['supportedPdoDrivers'] = \RainLoop\Pdo\Base::getAvailableDrivers();
|
||||
|
||||
$aResult['supportedPdoDrivers'] = \RainLoop\Common\PdoAbstract::getAvailableDrivers();
|
||||
$aResult['contactsEnable'] = (bool)$oConfig->Get('contacts', 'enable', false);
|
||||
$aResult['contactsSync'] = (bool)$oConfig->Get('contacts', 'allow_sync', false);
|
||||
$aResult['contactsPdoType'] = Providers\AddressBook\PdoAddressBook::validPdoType($oConfig->Get('contacts', 'type', 'sqlite'));
|
||||
$aResult['contactsPdoDsn'] = (string)$oConfig->Get('contacts', 'pdo_dsn', '');
|
||||
$aResult['contactsPdoType'] = (string)$oConfig->Get('contacts', 'type', '');
|
||||
$aResult['contactsPdoUser'] = (string)$oConfig->Get('contacts', 'pdo_user', '');
|
||||
$aResult['contactsPdoPassword'] = static::APP_DUMMY;
|
||||
$aResult['contactsMySQLSSLCA'] = (string) $oConfig->Get('contacts', 'mysql_ssl_ca', '');
|
||||
$aResult['contactsMySQLSSLVerify'] = !!$oConfig->Get('contacts', 'mysql_ssl_verify', true);
|
||||
$aResult['contactsMySQLSSLCiphers'] = (string) $oConfig->Get('contacts', 'mysql_ssl_ciphers', '');
|
||||
$aResult['contactsSuggestionsLimit'] = (int)$oConfig->Get('contacts', 'suggestions_limit', 20);
|
||||
|
||||
$aResult['ContactsEnable'] = (bool)$oConfig->Get('contacts', 'enable', false);
|
||||
$aResult['ContactsSync'] = (bool)$oConfig->Get('contacts', 'allow_sync', false);
|
||||
$aResult['ContactsPdoType'] = Providers\AddressBook\PdoAddressBook::validPdoType($this->oConfig->Get('contacts', 'type', 'sqlite'));
|
||||
$aResult['ContactsPdoDsn'] = (string)$oConfig->Get('contacts', 'pdo_dsn', '');
|
||||
$aResult['ContactsPdoType'] = (string)$oConfig->Get('contacts', 'type', '');
|
||||
$aResult['ContactsPdoUser'] = (string)$oConfig->Get('contacts', 'pdo_user', '');
|
||||
$aResult['ContactsPdoPassword'] = (string)APP_DUMMY;
|
||||
$aResult['faviconUrl'] = $oConfig->Get('webmail', 'favicon_url', '');
|
||||
|
||||
$aResult['FaviconUrl'] = $oConfig->Get('webmail', 'favicon_url', '');
|
||||
|
||||
$aResult['WeakPassword'] = \is_file(APP_PRIVATE_DATA.'admin_password.txt');
|
||||
$aResult['weakPassword'] = \is_file(APP_PRIVATE_DATA.'admin_password.txt');
|
||||
|
||||
$aResult['System']['languagesAdmin'] = \SnappyMail\L10n::getLanguages(true);
|
||||
$aResult['LanguageAdmin'] = $this->ValidateLanguage($oConfig->Get('webmail', 'language_admin', 'en'), '', true);
|
||||
$aResult['UserLanguageAdmin'] = $this->ValidateLanguage($UserLanguageRaw, '', true, true);
|
||||
$aResult['languageAdmin'] = $this->ValidateLanguage($oConfig->Get('webmail', 'language_admin', 'en'), '', true);
|
||||
$aResult['languageUsers'] = $this->ValidateLanguage($UserLanguageRaw, '', true, true);
|
||||
} else {
|
||||
$passfile = APP_PRIVATE_DATA.'admin_password.txt';
|
||||
$sPassword = $oConfig->Get('security', 'admin_password', '');
|
||||
|
|
@ -674,45 +674,35 @@ class Actions
|
|||
$aResult = \array_merge($aResult, [
|
||||
'Auth' => true,
|
||||
'Email' => \MailSo\Base\Utils::IdnToUtf8($oAccount->Email()),
|
||||
'IncLogin' => $oAccount->IncLogin(),
|
||||
'OutLogin' => $oAccount->OutLogin(),
|
||||
'AccountHash' => $oAccount->Hash(),
|
||||
'AccountSignMe' => isset($_COOKIE[self::AUTH_SIGN_ME_TOKEN_KEY]),
|
||||
'MainEmail' => \MailSo\Base\Utils::IdnToUtf8($this->getMainAccountFromToken()->Email()),
|
||||
'MailToEmail' => '',
|
||||
'accountHash' => $oAccount->Hash(),
|
||||
'accountSignMe' => isset($_COOKIE[self::AUTH_SIGN_ME_TOKEN_KEY]),
|
||||
|
||||
'ContactsIsAllowed' => $this->AddressBookProvider($oAccount)->IsActive(),
|
||||
'ContactsSyncIsAllowed' => (bool)$oConfig->Get('contacts', 'allow_sync', false),
|
||||
'ContactsSyncInterval' => (int)$oConfig->Get('contacts', 'sync_interval', 20),
|
||||
'ContactsSyncMode' => 0,
|
||||
'ContactsSyncUrl' => '',
|
||||
'ContactsSyncUser' => '',
|
||||
'ContactsSyncPassword' => '',
|
||||
'contactsAllowed' => $this->AddressBookProvider($oAccount)->IsActive(),
|
||||
|
||||
'ViewHTML' => (bool) $oConfig->Get('defaults', 'view_html', true),
|
||||
'ShowImages' => (bool) $oConfig->Get('defaults', 'show_images', false),
|
||||
'ViewImages' => $oConfig->Get('defaults', 'view_images', 'ask'),
|
||||
'ViewImagesWhitelist' => '',
|
||||
'RemoveColors' => (bool) $oConfig->Get('defaults', 'remove_colors', false),
|
||||
'AllowStyles' => false,
|
||||
'ListInlineAttachments' => false,
|
||||
'CollapseBlockquotes' => true,
|
||||
'MaxBlockquotesLevel' => 0,
|
||||
'simpleAttachmentsList' => false,
|
||||
'listGrouped' => false,
|
||||
'MessagesPerPage' => (int) $oConfig->Get('webmail', 'messages_per_page', 25),
|
||||
'MessageReadDelay' => (int) $oConfig->Get('webmail', 'message_read_delay', 5),
|
||||
'MsgDefaultAction' => 1,
|
||||
'MsgDefaultAction' => (int) $oConfig->Get('defaults', 'msg_default_action', 1),
|
||||
'SoundNotification' => true,
|
||||
'NotificationSound' => 'new-mail',
|
||||
'DesktopNotifications' => true,
|
||||
'Layout' => (int) $oConfig->Get('defaults', 'view_layout', Enumerations\Layout::SIDE_PREVIEW),
|
||||
'EditorDefaultType' => \str_replace('Forced', '', $oConfig->Get('defaults', 'view_editor_type', '')),
|
||||
'UseCheckboxesInList' => (bool) $oConfig->Get('defaults', 'view_use_checkboxes', true),
|
||||
'showNextMessage' => (bool) $oConfig->Get('defaults', 'view_show_next_message', false),
|
||||
'AutoLogout' => (int) $oConfig->Get('defaults', 'autologout', 30),
|
||||
'UseThreads' => (bool) $oConfig->Get('defaults', 'mail_use_threads', false),
|
||||
'AllowDraftAutosave' => (bool) $oConfig->Get('defaults', 'allow_draft_autosave', true),
|
||||
'ReplySameFolder' => (bool) $oConfig->Get('defaults', 'mail_reply_same_folder', false),
|
||||
'ContactsAutosave' => (bool) $oConfig->Get('defaults', 'contacts_autosave', true),
|
||||
'HideUnsubscribed' => false,
|
||||
'HideDeleted' => true,
|
||||
'UnhideKolabFolders' => false,
|
||||
'UserBackgroundName' => '',
|
||||
'UserBackgroundHash' => '',
|
||||
'SieveAllowFileintoInbox' => (bool)$oConfig->Get('labs', 'sieve_allow_fileinto_inbox', false)
|
||||
'sieveAllowFileintoInbox' => (bool)$oConfig->Get('labs', 'sieve_allow_fileinto_inbox', false)
|
||||
]);
|
||||
|
||||
$aAttachmentsActions = array();
|
||||
|
|
@ -723,9 +713,6 @@ class Actions
|
|||
}
|
||||
$aResult['System'] = \array_merge(
|
||||
$aResult['System'], array(
|
||||
'allowHtmlEditorBitiButtons' => (bool)$oConfig->Get('labs', 'allow_html_editor_biti_buttons', false),
|
||||
'allowCtrlEnterOnCompose' => (bool)$oConfig->Get('labs', 'allow_ctrl_enter_on_compose', false),
|
||||
'useImapThread' => (bool)$oConfig->Get('imap', 'use_thread', false),
|
||||
'allowAppendMessage' => (bool)$oConfig->Get('labs', 'allow_message_append', false),
|
||||
'folderSpecLimit' => (int)$oConfig->Get('labs', 'folders_spec_limit', 50),
|
||||
'listPermanentFiltered' => '' !== \trim($oConfig->Get('imap', 'message_list_permanent_filter', '')),
|
||||
|
|
@ -734,49 +721,48 @@ class Actions
|
|||
)
|
||||
);
|
||||
|
||||
if ($aResult['ContactsIsAllowed'] && $aResult['ContactsSyncIsAllowed']) {
|
||||
$mData = $this->getContactsSyncData($oAccount);
|
||||
if (\is_array($mData)) {
|
||||
$aResult['ContactsSyncMode'] = isset($mData['Mode']) ? $mData['Mode'] : 0;
|
||||
$aResult['ContactsSyncUrl'] = isset($mData['Url']) ? \trim($mData['Url']) : '';
|
||||
$aResult['ContactsSyncUser'] = isset($mData['User']) ? \trim($mData['User']) : '';
|
||||
$aResult['ContactsSyncPassword'] = APP_DUMMY;
|
||||
}
|
||||
if ($aResult['contactsAllowed'] && $oConfig->Get('contacts', 'allow_sync', false)) {
|
||||
$aData = $this->getContactsSyncData($oAccount) ?: [
|
||||
'Mode' => 0,
|
||||
'Url' => '',
|
||||
'User' => ''
|
||||
];
|
||||
$aData['Password'] = empty($aData['Password']) ? '' : static::APP_DUMMY;
|
||||
$aData['Interval'] = \max(20, \min(320, (int) $oConfig->Get('contacts', 'sync_interval', 20)));
|
||||
unset($aData['PasswordHMAC']);
|
||||
$aResult['ContactsSync'] = $aData;
|
||||
}
|
||||
|
||||
$sToken = Utils::GetCookie(self::AUTH_MAILTO_TOKEN_KEY, null);
|
||||
$sToken = \SnappyMail\Cookies::get(self::AUTH_MAILTO_TOKEN_KEY);
|
||||
if (null !== $sToken) {
|
||||
Utils::ClearCookie(self::AUTH_MAILTO_TOKEN_KEY);
|
||||
\SnappyMail\Cookies::clear(self::AUTH_MAILTO_TOKEN_KEY);
|
||||
|
||||
$mMailToData = Utils::DecodeKeyValuesQ($sToken);
|
||||
if (!empty($mMailToData['MailTo']) && 'MailTo' === $mMailToData['MailTo'] && !empty($mMailToData['To'])) {
|
||||
$aResult['MailToEmail'] = \MailSo\Base\Utils::IdnToUtf8($mMailToData['To']);
|
||||
$aResult['mailToEmail'] = \MailSo\Base\Utils::IdnToUtf8($mMailToData['To']);
|
||||
}
|
||||
}
|
||||
|
||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||
if ($oSettingsLocal instanceof Settings) {
|
||||
$aResult['SentFolder'] = (string)$oSettingsLocal->GetConf('SentFolder', '');
|
||||
$aResult['DraftsFolder'] = (string)$oSettingsLocal->GetConf('DraftFolder', '');
|
||||
$aResult['SpamFolder'] = (string)$oSettingsLocal->GetConf('SpamFolder', '');
|
||||
$aResult['TrashFolder'] = (string)$oSettingsLocal->GetConf('TrashFolder', '');
|
||||
$aResult['ArchiveFolder'] = (string)$oSettingsLocal->GetConf('ArchiveFolder', '');
|
||||
$aResult['HideUnsubscribed'] = (bool)$oSettingsLocal->GetConf('HideUnsubscribed', $aResult['HideUnsubscribed']);
|
||||
$aResult['UseThreads'] = (bool)$oSettingsLocal->GetConf('UseThreads', $aResult['UseThreads']);
|
||||
$aResult['ReplySameFolder'] = (bool)$oSettingsLocal->GetConf('ReplySameFolder', $aResult['ReplySameFolder']);
|
||||
$aResult['HideDeleted'] = (bool)$oSettingsLocal->GetConf('HideDeleted', $aResult['HideDeleted']);
|
||||
$aResult['UnhideKolabFolders'] = (bool)$oSettingsLocal->GetConf('UnhideKolabFolders', $aResult['UnhideKolabFolders']);
|
||||
}
|
||||
|
||||
if ($oConfig->Get('login', 'determine_user_language', true)) {
|
||||
$sLanguage = $this->ValidateLanguage($UserLanguageRaw, $sLanguage, false);
|
||||
}
|
||||
|
||||
// MainAccount or AdditionalAccount
|
||||
$aResult = \array_merge($aResult, $this->getAccountData($oAccount));
|
||||
|
||||
// MainAccount
|
||||
$oSettings = $this->SettingsProvider()->Load($oAccount);
|
||||
if ($oSettings instanceof Settings) {
|
||||
if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)) {
|
||||
$sLanguage = (string) $oSettings->GetConf('Language', $sLanguage);
|
||||
/*
|
||||
foreach ($oSettings->toArray() as $key => $value) {
|
||||
$aResult[\lcfirst($key)] = $value;
|
||||
}
|
||||
*/
|
||||
if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)) {
|
||||
$sLanguage = (string) $oSettings->GetConf('language', $sLanguage);
|
||||
}
|
||||
$aResult['hourCycle'] = $oSettings->GetConf('hourCycle', '');
|
||||
|
||||
if (!$oSettings->GetConf('MessagesPerPage')) {
|
||||
$oSettings->SetConf('MessagesPerPage', $oSettings->GetConf('MPP', $aResult['MessagesPerPage']));
|
||||
}
|
||||
|
|
@ -784,13 +770,23 @@ class Actions
|
|||
$aResult['EditorDefaultType'] = \str_replace('Forced', '', $oSettings->GetConf('EditorDefaultType', $aResult['EditorDefaultType']));
|
||||
$aResult['requestReadReceipt'] = (bool) $oSettings->GetConf('requestReadReceipt', false);
|
||||
$aResult['requestDsn'] = (bool) $oSettings->GetConf('requestDsn', false);
|
||||
$aResult['requireTLS'] = (bool) $oSettings->GetConf('requireTLS', false);
|
||||
$aResult['pgpSign'] = (bool) $oSettings->GetConf('pgpSign', false);
|
||||
$aResult['pgpEncrypt'] = (bool) $oSettings->GetConf('pgpEncrypt', false);
|
||||
$aResult['allowSpellcheck'] = (bool) $oSettings->GetConf('allowSpellcheck', false);
|
||||
// $aResult['allowCtrlEnterOnCompose'] = (bool) $oSettings->GetConf('allowCtrlEnterOnCompose', true);
|
||||
|
||||
$aResult['ViewHTML'] = (bool)$oSettings->GetConf('ViewHTML', $aResult['ViewHTML']);
|
||||
$aResult['ShowImages'] = (bool)$oSettings->GetConf('ShowImages', $aResult['ShowImages']);
|
||||
$show_images = (bool) $oSettings->GetConf('ShowImages', false);
|
||||
$aResult['ViewImages'] = $oSettings->GetConf('ViewImages', $show_images ? 'always' : $aResult['ViewImages']);
|
||||
$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']);
|
||||
$aResult['simpleAttachmentsList'] = (bool)$oSettings->GetConf('simpleAttachmentsList', $aResult['simpleAttachmentsList']);
|
||||
$aResult['listGrouped'] = (bool)$oSettings->GetConf('listGrouped', $aResult['listGrouped']);
|
||||
$aResult['ContactsAutosave'] = (bool)$oSettings->GetConf('ContactsAutosave', $aResult['ContactsAutosave']);
|
||||
$aResult['MessagesPerPage'] = (int)$oSettings->GetConf('MessagesPerPage', $aResult['MessagesPerPage']);
|
||||
$aResult['MessageReadDelay'] = (int)$oSettings->GetConf('MessageReadDelay', $aResult['MessageReadDelay']);
|
||||
|
|
@ -799,6 +795,7 @@ class Actions
|
|||
$aResult['NotificationSound'] = (string)$oSettings->GetConf('NotificationSound', $aResult['NotificationSound']);
|
||||
$aResult['DesktopNotifications'] = (bool)$oSettings->GetConf('DesktopNotifications', $aResult['DesktopNotifications']);
|
||||
$aResult['UseCheckboxesInList'] = (bool)$oSettings->GetConf('UseCheckboxesInList', $aResult['UseCheckboxesInList']);
|
||||
$aResult['showNextMessage'] = (bool)$oSettings->GetConf('showNextMessage', $aResult['showNextMessage']);
|
||||
$aResult['AllowDraftAutosave'] = (bool)$oSettings->GetConf('AllowDraftAutosave', $aResult['AllowDraftAutosave']);
|
||||
$aResult['AutoLogout'] = (int)$oSettings->GetConf('AutoLogout', $aResult['AutoLogout']);
|
||||
$aResult['Layout'] = (int)$oSettings->GetConf('Layout', $aResult['Layout']);
|
||||
|
|
@ -811,14 +808,14 @@ class Actions
|
|||
$aResult['fontMono'] = $oSettings->GetConf('fontMono', '');
|
||||
|
||||
if ($this->GetCapa(Enumerations\Capa::USER_BACKGROUND)) {
|
||||
$aResult['UserBackgroundName'] = (string)$oSettings->GetConf('UserBackgroundName', $aResult['UserBackgroundName']);
|
||||
$aResult['UserBackgroundHash'] = (string)$oSettings->GetConf('UserBackgroundHash', $aResult['UserBackgroundHash']);
|
||||
$aResult['userBackgroundName'] = (string)$oSettings->GetConf('UserBackgroundName', '');
|
||||
$aResult['userBackgroundHash'] = (string)$oSettings->GetConf('UserBackgroundHash', '');
|
||||
}
|
||||
}
|
||||
|
||||
$aResult['NewMailSounds'] = [];
|
||||
$aResult['newMailSounds'] = [];
|
||||
foreach (\glob(APP_VERSION_ROOT_PATH.'static/sounds/*.mp3') as $file) {
|
||||
$aResult['NewMailSounds'][] = \basename($file, '.mp3');
|
||||
$aResult['newMailSounds'][] = \basename($file, '.mp3');
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
|
@ -826,7 +823,7 @@ class Actions
|
|||
$sLanguage = $this->ValidateLanguage($UserLanguageRaw, $sLanguage, false);
|
||||
}
|
||||
|
||||
if ('0.0.0' === APP_VERSION) {
|
||||
if (SNAPPYMAIL_DEV) {
|
||||
$aResult['DevEmail'] = $oConfig->Get('labs', 'dev_email', '');
|
||||
$aResult['DevPassword'] = $oConfig->Get('labs', 'dev_password', '');
|
||||
} else {
|
||||
|
|
@ -834,15 +831,13 @@ class Actions
|
|||
$aResult['DevPassword'] = '';
|
||||
}
|
||||
|
||||
$aResult['SignMe'] = (string) $oConfig->Get('login', 'sign_me_auto', Enumerations\SignMeType::DEFAULT_OFF);
|
||||
|
||||
$aResult['AdditionalLoginError'] = $this->GetSpecLogoutCustomMgsWithDeletion();
|
||||
$aResult['signMe'] = (string) $oConfig->Get('login', 'sign_me_auto', Enumerations\SignMeType::DEFAULT_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
if ($aResult['Auth']) {
|
||||
$aResult['UseLocalProxyForExternalImages'] = (bool)$oConfig->Get('labs', 'use_local_proxy_for_external_images', false);
|
||||
$aResult['AllowLanguagesOnSettings'] = (bool) $oConfig->Get('webmail', 'allow_languages_on_settings', true);
|
||||
$aResult['useLocalProxyForExternalImages'] = (bool)$oConfig->Get('labs', 'use_local_proxy_for_external_images', false);
|
||||
$aResult['allowLanguagesOnSettings'] = (bool) $oConfig->Get('webmail', 'allow_languages_on_settings', true);
|
||||
$aResult['Capa'] = $this->Capa($bAdmin, $oAccount);
|
||||
$value = \ini_get('upload_max_filesize');
|
||||
$upload_max_filesize = \intval($value);
|
||||
|
|
@ -851,26 +846,25 @@ class Actions
|
|||
case 'M': $upload_max_filesize *= 1024;
|
||||
case 'K': $upload_max_filesize *= 1024;
|
||||
}
|
||||
$aResult['AttachmentLimit'] = \min($upload_max_filesize, ((int) $oConfig->Get('webmail', 'attachment_size_limit', 10)) * 1024 * 1024);
|
||||
$aResult['PhpUploadSizes'] = array(
|
||||
$aResult['attachmentLimit'] = \min($upload_max_filesize, ((int) $oConfig->Get('webmail', 'attachment_size_limit', 10)) * 1024 * 1024);
|
||||
$aResult['phpUploadSizes'] = array(
|
||||
'upload_max_filesize' => $value,
|
||||
'post_max_size' => \ini_get('post_max_size')
|
||||
);
|
||||
$aResult['System']['themes'] = $this->GetThemes();
|
||||
}
|
||||
|
||||
$sStaticCache = $this->StaticCache();
|
||||
|
||||
$aResult['Theme'] = $this->GetTheme($bAdmin);
|
||||
|
||||
$aResult['Language'] = $this->ValidateLanguage($sLanguage, '', false);
|
||||
$aResult['UserLanguage'] = $this->ValidateLanguage($UserLanguageRaw, '', false, true);
|
||||
$aResult['language'] = $this->ValidateLanguage($sLanguage, '', false);
|
||||
$aResult['userLanguage'] = $this->ValidateLanguage($UserLanguageRaw, '', false, true);
|
||||
|
||||
$aResult['PluginsLink'] = $this->oPlugins->HaveJs($bAdmin)
|
||||
? './?/Plugins/0/' . ($bAdmin ? 'Admin' : 'User') . '/' . $sStaticCache . '/'
|
||||
? 'Plugins/0/' . ($bAdmin ? 'Admin' : 'User') . '/' . $this->etag($this->oPlugins->Hash()) . '/'
|
||||
: '';
|
||||
|
||||
$bAppJsDebug = $this->oConfig->Get('labs', 'use_app_debug_js', false);
|
||||
$bAppJsDebug = $this->oConfig->Get('debug', 'javascript', false)
|
||||
|| $this->oConfig->Get('debug', 'enable', false);
|
||||
|
||||
$aResult['StaticLibsJs'] = Utils::WebStaticPath('js/' . ($bAppJsDebug ? '' : 'min/') .
|
||||
'libs' . ($bAppJsDebug ? '' : '.min') . '.js');
|
||||
|
|
@ -880,25 +874,20 @@ class Actions
|
|||
return $aResult;
|
||||
}
|
||||
|
||||
protected function requestSleep(int $iDelay = 1): void
|
||||
{
|
||||
$time = \microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
if ($iDelay > $time) {
|
||||
\usleep(($iDelay - $time) * 1000000);
|
||||
}
|
||||
}
|
||||
|
||||
protected function loginErrorDelay(): void
|
||||
{
|
||||
$iDelay = (int)$this->oConfig->Get('labs', 'login_fault_delay', 0);
|
||||
$iDelay = (int) $this->oConfig->Get('labs', 'login_fault_delay', 0);
|
||||
if (0 < $iDelay) {
|
||||
$this->requestSleep($iDelay);
|
||||
$seconds = $iDelay - (\microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']);
|
||||
if (0 < $seconds) {
|
||||
\usleep(\intval($seconds * 1000000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getAdditionalLogParamsByUserLogin(string $sLogin, bool $bAdmin = false): array
|
||||
{
|
||||
$sHost = $bAdmin ? $this->Http()->GetHost(false, true, true) : \MailSo\Base\Utils::GetDomainFromEmail($sLogin);
|
||||
$sHost = $bAdmin ? $this->Http()->GetHost(true, true) : \MailSo\Base\Utils::GetDomainFromEmail($sLogin);
|
||||
return array(
|
||||
'{imap:login}' => $sLogin,
|
||||
'{imap:host}' => $sHost,
|
||||
|
|
@ -912,13 +901,12 @@ class Actions
|
|||
|
||||
public function DoPing(): array
|
||||
{
|
||||
return $this->DefaultResponse(__FUNCTION__, 'Pong');
|
||||
return $this->DefaultResponse('Pong');
|
||||
}
|
||||
|
||||
public function DoVersion(): array
|
||||
{
|
||||
return $this->DefaultResponse(__FUNCTION__,
|
||||
APP_VERSION === (string)$this->GetActionParam('Version', ''));
|
||||
return $this->DefaultResponse(APP_VERSION === (string)$this->GetActionParam('version', ''));
|
||||
}
|
||||
|
||||
public function MainClearFileName(string $sFileName, string $sContentType, string $sMimeIndex, int $iMaxLength = 250): string
|
||||
|
|
@ -934,82 +922,38 @@ class Actions
|
|||
return \MailSo\Base\Utils::SecureFileName(\MailSo\Base\Utils::Utf8Clear($sClearedFileName));
|
||||
}
|
||||
|
||||
protected function getUploadErrorMessageByCode(int $iError, int &$iClientError): string
|
||||
{
|
||||
$sError = '';
|
||||
$iClientError = UploadError::NORMAL;
|
||||
switch ($iError) {
|
||||
case UPLOAD_ERR_OK:
|
||||
break;
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
case UploadError::CONFIG_SIZE:
|
||||
case UploadError::EMPTY_FILES_DATA:
|
||||
$sError = 'File is too big';
|
||||
$iClientError = UploadError::FILE_IS_TOO_BIG;
|
||||
break;
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
$sError = 'File partially uploaded';
|
||||
$iClientError = UploadError::FILE_PARTIALLY_UPLOADED;
|
||||
break;
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
$sError = 'No file uploaded';
|
||||
$iClientError = UploadError::FILE_NO_UPLOADED;
|
||||
break;
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
$sError = 'Missing temp folder';
|
||||
$iClientError = UploadError::MISSING_TEMP_FOLDER;
|
||||
break;
|
||||
case UploadError::ON_SAVING:
|
||||
$sError = 'Error on saving file';
|
||||
$iClientError = UploadError::FILE_ON_SAVING_ERROR;
|
||||
break;
|
||||
case UploadError::FILE_TYPE:
|
||||
$sError = 'Invalid file type';
|
||||
$iClientError = UploadError::FILE_TYPE;
|
||||
break;
|
||||
case UploadError::UNKNOWN:
|
||||
default:
|
||||
$sError = 'Unknown error';
|
||||
$iClientError = UploadError::UNKNOWN;
|
||||
break;
|
||||
}
|
||||
|
||||
return $sError;
|
||||
}
|
||||
|
||||
public function Upload(): array
|
||||
public function Upload(?array $aFile, int $iError): array
|
||||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
$aResponse = array();
|
||||
|
||||
$aFile = $this->GetActionParam('File', null);
|
||||
$iError = $this->GetActionParam('Error', Enumerations\UploadError::UNKNOWN);
|
||||
|
||||
if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) {
|
||||
$sSavedName = 'upload-post-' . \md5($aFile['name'] . $aFile['tmp_name']);
|
||||
|
||||
// Detect content-type
|
||||
$type = \SnappyMail\File\MimeType::fromFile($aFile['tmp_name'], $aFile['name'])
|
||||
?: \SnappyMail\File\MimeType::fromFilename($aFile['name']);
|
||||
if ($type) {
|
||||
$aFile['type'] = $type;
|
||||
$sSavedName .= \SnappyMail\File\MimeType::toExtension($type);
|
||||
}
|
||||
|
||||
if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) {
|
||||
$iError = Enumerations\UploadError::ON_SAVING;
|
||||
} else {
|
||||
$sUploadName = $aFile['name'];
|
||||
$iSize = $aFile['size'];
|
||||
$sMimeType = $aFile['type'];
|
||||
|
||||
$aResponse['Attachment'] = array(
|
||||
'Name' => $sUploadName,
|
||||
'TempName' => $sSavedName,
|
||||
'MimeType' => $sMimeType,
|
||||
'Size' => (int)$iSize
|
||||
'name' => $aFile['name'],
|
||||
'tempName' => $sSavedName,
|
||||
'mimeType' => $aFile['type'],
|
||||
'size' => (int) $aFile['size']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (UPLOAD_ERR_OK !== $iError) {
|
||||
$iClientError = Enumerations\UploadError::NORMAL;
|
||||
$sError = $this->getUploadErrorMessageByCode($iError, $iClientError);
|
||||
$iClientError = 0;
|
||||
$sError = Enumerations\UploadError::getUserMessage($iError, $iClientError);
|
||||
|
||||
if (!empty($sError)) {
|
||||
$aResponse['ErrorCode'] = $iClientError;
|
||||
|
|
@ -1017,88 +961,7 @@ class Actions
|
|||
}
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $aResponse);
|
||||
}
|
||||
|
||||
public function UploadBackground(): array
|
||||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
if (!$this->GetCapa(Enumerations\Capa::USER_BACKGROUND)) {
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
}
|
||||
|
||||
$sName = '';
|
||||
$sHash = '';
|
||||
|
||||
$aFile = $this->GetActionParam('File', null);
|
||||
$iError = $this->GetActionParam('Error', Enumerations\UploadError::UNKNOWN);
|
||||
|
||||
if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) {
|
||||
$sMimeType = \strtolower(\MailSo\Base\Utils::MimeContentType($aFile['name']));
|
||||
if (\in_array($sMimeType, array('image/png', 'image/jpg', 'image/jpeg'))) {
|
||||
$sSavedName = 'upload-post-' . \md5($aFile['name'] . $aFile['tmp_name']);
|
||||
if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) {
|
||||
$iError = Enumerations\UploadError::ON_SAVING;
|
||||
} else {
|
||||
$rData = $this->FilesProvider()->GetFile($oAccount, $sSavedName);
|
||||
if (\is_resource($rData)) {
|
||||
$sData = \stream_get_contents($rData);
|
||||
if (!empty($sData) && \strlen($sData)) {
|
||||
$sName = $aFile['name'];
|
||||
if (empty($sName)) {
|
||||
$sName = '_';
|
||||
}
|
||||
|
||||
if ($this->StorageProvider()->Put($oAccount,
|
||||
Providers\Storage\Enumerations\StorageType::CONFIG,
|
||||
'background',
|
||||
Utils::jsonEncode(array(
|
||||
'Name' => $aFile['name'],
|
||||
'ContentType' => $sMimeType,
|
||||
'Raw' => \base64_encode($sData)
|
||||
))
|
||||
)) {
|
||||
$oSettings = $this->SettingsProvider()->Load($oAccount);
|
||||
if ($oSettings) {
|
||||
$sHash = \MailSo\Base\Utils::Sha1Rand($sName . APP_VERSION . APP_SALT);
|
||||
|
||||
$oSettings->SetConf('UserBackgroundName', $sName);
|
||||
$oSettings->SetConf('UserBackgroundHash', $sHash);
|
||||
$this->SettingsProvider()->Save($oAccount, $oSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($sData);
|
||||
}
|
||||
|
||||
if (\is_resource($rData)) {
|
||||
\fclose($rData);
|
||||
}
|
||||
|
||||
unset($rData);
|
||||
}
|
||||
|
||||
$this->FilesProvider()->Clear($oAccount, $sSavedName);
|
||||
} else {
|
||||
$iError = Enumerations\UploadError::FILE_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
if (UPLOAD_ERR_OK !== $iError) {
|
||||
$iClientError = Enumerations\UploadError::NORMAL;
|
||||
$sError = $this->getUploadErrorMessageByCode($iError, $iClientError);
|
||||
|
||||
if (!empty($sError)) {
|
||||
return $this->FalseResponse(__FUNCTION__, $iClientError, $sError);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, !empty($sName) && !empty($sHash) ? array(
|
||||
'Name' => $sName,
|
||||
'Hash' => $sHash
|
||||
) : false);
|
||||
return $this->DefaultResponse($aResponse);
|
||||
}
|
||||
|
||||
public function Capa(bool $bAdmin, ?Model\Account $oAccount = null): array
|
||||
|
|
@ -1108,14 +971,18 @@ class Actions
|
|||
$oConfig = $this->oConfig;
|
||||
$aResult = array(
|
||||
'AdditionalAccounts' => (bool) $oConfig->Get('webmail', 'allow_additional_accounts', false),
|
||||
'AttachmentThumbnails' => (bool) $oConfig->Get('interface', 'show_attachment_thumbnail', true),
|
||||
'AttachmentThumbnails' => (bool) $oConfig->Get('interface', 'show_attachment_thumbnail', true)
|
||||
&& ($bAdmin
|
||||
|| \extension_loaded('gd')
|
||||
|| \extension_loaded('gmagick')
|
||||
|| \extension_loaded('imagick')
|
||||
),
|
||||
'AttachmentsActions' => (bool) $oConfig->Get('capa', 'attachments_actions', false),
|
||||
'Contacts' => (bool) $oConfig->Get('contacts', 'enable', false),
|
||||
'DangerousActions' => (bool) $oConfig->Get('capa', 'dangerous_actions', true),
|
||||
'GnuPG' => (bool) $oConfig->Get('security', 'openpgp', false) && \SnappyMail\PGP\GnuPG::isSupported(),
|
||||
'Identities' => (bool) $oConfig->Get('webmail', 'allow_additional_identities', false),
|
||||
'Kolab' => false, // See Kolab plugin
|
||||
'MessageActions' => (bool) $oConfig->Get('capa', 'message_actions', true),
|
||||
'OpenPGP' => (bool) $oConfig->Get('security', 'openpgp', false),
|
||||
'Quota' => (bool) $oConfig->Get('capa', 'quota', true),
|
||||
'Sieve' => false,
|
||||
|
|
@ -1134,36 +1001,29 @@ class Actions
|
|||
|
||||
public function etag(string $sKey): string
|
||||
{
|
||||
return \md5('Etag:' . \md5($sKey . \md5($this->oConfig->Get('cache', 'index', ''))));
|
||||
// if ($sKey && $this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true)) {
|
||||
return \md5($sKey . $this->oConfig->Get('cache', 'index', '') . APP_VERSION);
|
||||
}
|
||||
|
||||
public function cacheByKey(string $sKey, bool $bForce = false): bool
|
||||
public function cacheByKey(string $sKey): 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;
|
||||
}
|
||||
if ($sKey && $this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true)) {
|
||||
\MailSo\Base\Http::ServerUseCache(
|
||||
$this->etag($sKey),
|
||||
0, // issue with messages
|
||||
$this->oConfig->Get('cache', 'http_expires', 3600)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$bResult) {
|
||||
$this->Http()->ServerNoCache();
|
||||
}
|
||||
|
||||
return $bResult;
|
||||
$this->Http()->ServerNoCache();
|
||||
return false;
|
||||
}
|
||||
|
||||
public function verifyCacheByKey(string $sKey, bool $bForce = false): void
|
||||
public function verifyCacheByKey(string $sKey): 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 && $this->oConfig->Get('cache', 'enable', true) && $this->oConfig->Get('cache', 'http', true)) {
|
||||
\MailSo\Base\Http::checkETag($this->etag($sKey));
|
||||
// \MailSo\Base\Http::checkLastModified(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1174,9 +1034,9 @@ class Actions
|
|||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
if (!$this->MailClient()->IsLoggined()) {
|
||||
if ($oAccount && !$this->ImapClient()->IsLoggined()) {
|
||||
try {
|
||||
$oAccount->ImapConnectAndLoginHelper($this->oPlugins, $this->MailClient(), $this->oConfig);
|
||||
$oAccount->ImapConnectAndLogin($this->oPlugins, $this->ImapClient(), $this->oConfig);
|
||||
} catch (\MailSo\Net\Exceptions\ConnectionException $oException) {
|
||||
throw new Exceptions\ClientException(Notifications::ConnectionError, $oException);
|
||||
} catch (\Throwable $oException) {
|
||||
|
|
@ -1187,18 +1047,21 @@ class Actions
|
|||
return $oAccount;
|
||||
}
|
||||
|
||||
protected function getDecodedRawKeyValue(string $sRawKey): array
|
||||
public function encodeRawKey(array $aValues): string
|
||||
{
|
||||
return empty($sRawKey) ? array() : Utils::DecodeKeyValuesQ($sRawKey);
|
||||
$aValues['accountHash'] = $this->getAccountFromToken()->Hash();
|
||||
return \MailSo\Base\Utils::UrlSafeBase64Encode(\json_encode($aValues));
|
||||
}
|
||||
|
||||
public function StaticCache(): string
|
||||
public function decodeRawKey(string $sRawKey): array
|
||||
{
|
||||
static $sCache = null;
|
||||
if (!$sCache) {
|
||||
$sCache = \md5(APP_VERSION . $this->oPlugins->Hash());
|
||||
return empty($sRawKey) ? []
|
||||
: (\json_decode(\MailSo\Base\Utils::UrlSafeBase64Decode($sRawKey), true) ?: []);
|
||||
/*
|
||||
if (empty($aValues['accountHash']) || $aValues['accountHash'] !== $oAccount->Hash()) {
|
||||
return [];
|
||||
}
|
||||
return $sCache;
|
||||
*/
|
||||
}
|
||||
|
||||
public function SetActionParams(array $aCurrentActionParams, string $sMethodName = ''): self
|
||||
|
|
@ -1231,10 +1094,10 @@ class Actions
|
|||
return isset($this->aCurrentActionParams[$sKey]);
|
||||
}
|
||||
|
||||
public function Location(string $sUrl): void
|
||||
public function Location(string $sUrl, int $iStatus = 302): void
|
||||
{
|
||||
$this->oLogger->Write('Location: ' . $sUrl);
|
||||
\header('Location: ' . $sUrl);
|
||||
$this->logWrite("{$iStatus} Location: {$sUrl}");
|
||||
\MailSo\Base\Http::Location($sUrl, $iStatus);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,10 +15,7 @@ use RainLoop\Utils;
|
|||
|
||||
trait Accounts
|
||||
{
|
||||
/**
|
||||
* @var RainLoop\Providers\Identities
|
||||
*/
|
||||
private $oIdentitiesProvider;
|
||||
private ?\RainLoop\Providers\Identities $oIdentitiesProvider = null;
|
||||
|
||||
protected function GetMainEmail(Account $oAccount)
|
||||
{
|
||||
|
|
@ -41,7 +38,8 @@ trait Accounts
|
|||
StorageType::CONFIG,
|
||||
'additionalaccounts'
|
||||
);
|
||||
$aAccounts = $sAccounts ? \json_decode($sAccounts, true) : \SnappyMail\Upgrade::ConvertInsecureAccounts($this, $oAccount);
|
||||
$aAccounts = $sAccounts ? \json_decode($sAccounts, true)
|
||||
: \SnappyMail\Upgrade::ConvertInsecureAccounts($this, $oAccount);
|
||||
if ($aAccounts && \is_array($aAccounts)) {
|
||||
return $aAccounts;
|
||||
}
|
||||
|
|
@ -77,14 +75,15 @@ trait Accounts
|
|||
$oMainAccount = $this->getMainAccountFromToken();
|
||||
|
||||
if (!$this->GetCapa(Capa::ADDITIONAL_ACCOUNTS)) {
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
$aAccounts = $this->GetAccounts($oMainAccount);
|
||||
|
||||
$sEmail = \trim($this->GetActionParam('Email', ''));
|
||||
$sPassword = $this->GetActionParam('Password', '');
|
||||
$bNew = '1' === (string)$this->GetActionParam('New', '1');
|
||||
$sEmail = \trim($this->GetActionParam('email', ''));
|
||||
$sPassword = $this->GetActionParam('password', '');
|
||||
$sName = \trim($this->GetActionParam('name', ''));
|
||||
$bNew = !empty($this->GetActionParam('new', 1));
|
||||
|
||||
$sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
||||
if ($bNew && ($oMainAccount->Email() === $sEmail || isset($aAccounts[$sEmail]))) {
|
||||
|
|
@ -93,12 +92,73 @@ trait Accounts
|
|||
throw new ClientException(Notifications::AccountDoesNotExist);
|
||||
}
|
||||
|
||||
$oNewAccount = $this->LoginProcess($sEmail, $sPassword, false, false);
|
||||
if ($bNew || $sPassword) {
|
||||
$oNewAccount = $this->LoginProcess($sEmail, $sPassword, false, false);
|
||||
$aAccounts[$sEmail] = $oNewAccount->asTokenArray($oMainAccount);
|
||||
} else {
|
||||
$aAccounts[$sEmail] = \RainLoop\Model\AdditionalAccount::convertArray($aAccounts[$sEmail]);
|
||||
}
|
||||
|
||||
$aAccounts[$oNewAccount->Email()] = $oNewAccount->asTokenArray($oMainAccount);
|
||||
$this->SetAccounts($oMainAccount, $aAccounts);
|
||||
if ($aAccounts[$sEmail]) {
|
||||
$aAccounts[$sEmail]['name'] = $sName;
|
||||
$this->SetAccounts($oMainAccount, $aAccounts);
|
||||
}
|
||||
|
||||
return $this->TrueResponse(__FUNCTION__);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
|
||||
protected function loadAdditionalAccountImapClient(string $sEmail): \MailSo\Imap\ImapClient
|
||||
{
|
||||
$sEmail = \MailSo\Base\Utils::IdnToAscii(\trim($sEmail), true);
|
||||
if (!\strlen($sEmail)) {
|
||||
throw new ClientException(Notifications::AccountDoesNotExist);
|
||||
}
|
||||
|
||||
$oMainAccount = $this->getMainAccountFromToken();
|
||||
$aAccounts = $this->GetAccounts($oMainAccount);
|
||||
if (!isset($aAccounts[$sEmail])) {
|
||||
throw new ClientException(Notifications::AccountDoesNotExist);
|
||||
}
|
||||
$oAccount = AdditionalAccount::NewInstanceFromTokenArray($this, $aAccounts[$sEmail]);
|
||||
if (!$oAccount) {
|
||||
throw new ClientException(Notifications::AccountDoesNotExist);
|
||||
}
|
||||
|
||||
$oImapClient = new \MailSo\Imap\ImapClient;
|
||||
$oImapClient->SetLogger($this->Logger());
|
||||
$this->imapConnect($oAccount, false, $oImapClient);
|
||||
return $oImapClient;
|
||||
}
|
||||
|
||||
public function DoAccountUnread(): array
|
||||
{
|
||||
$oImapClient = $this->loadAdditionalAccountImapClient($this->GetActionParam('email', ''));
|
||||
$oInfo = $oImapClient->FolderStatus('INBOX');
|
||||
return $this->DefaultResponse([
|
||||
'unreadEmails' => \max(0, $oInfo->UNSEEN)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports all mail from AdditionalAccount into MainAccount
|
||||
*/
|
||||
public function DoAccountImport(): array
|
||||
{
|
||||
$sEmail = $this->GetActionParam('email', '');
|
||||
$oImapSource = $this->loadAdditionalAccountImapClient($sEmail);
|
||||
|
||||
$oMainAccount = $this->getMainAccountFromToken();
|
||||
$oImapTarget = new \MailSo\Imap\ImapClient;
|
||||
$oImapTarget->SetLogger($this->Logger());
|
||||
$this->imapConnect($oMainAccount, false, $oImapTarget);
|
||||
|
||||
$oSync = new \SnappyMail\Imap\Sync;
|
||||
$oSync->oImapSource = $oImapSource;
|
||||
$oSync->oImapTarget = $oImapTarget;
|
||||
|
||||
$rootfolder = $this->GetActionParam('rootfolder', '') ?: $sEmail;
|
||||
$oSync->import($rootfolder);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -109,10 +169,10 @@ trait Accounts
|
|||
$oMainAccount = $this->getMainAccountFromToken();
|
||||
|
||||
if (!$this->GetCapa(Capa::ADDITIONAL_ACCOUNTS)) {
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
$sEmailToDelete = \trim($this->GetActionParam('EmailToDelete', ''));
|
||||
$sEmailToDelete = \trim($this->GetActionParam('emailToDelete', ''));
|
||||
$sEmailToDelete = \MailSo\Base\Utils::IdnToAscii($sEmailToDelete, true);
|
||||
|
||||
$aAccounts = $this->GetAccounts($oMainAccount);
|
||||
|
|
@ -121,17 +181,59 @@ trait Accounts
|
|||
$bReload = false;
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
if ($oAccount instanceof AdditionalAccount && $oAccount->Email() === $sEmailToDelete) {
|
||||
Utils::ClearCookie(self::AUTH_ADDITIONAL_TOKEN_KEY);
|
||||
\SnappyMail\Cookies::clear(self::AUTH_ADDITIONAL_TOKEN_KEY);
|
||||
$bReload = true;
|
||||
}
|
||||
|
||||
unset($aAccounts[$sEmailToDelete]);
|
||||
$this->SetAccounts($oMainAccount, $aAccounts);
|
||||
|
||||
return $this->TrueResponse(__FUNCTION__, array('Reload' => $bReload));
|
||||
return $this->TrueResponse(array('Reload' => $bReload));
|
||||
}
|
||||
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
public function getAccountData(Account $oAccount): array
|
||||
{
|
||||
$oConfig = $this->Config();
|
||||
$aResult = [
|
||||
'Email' => $oAccount->Email(),
|
||||
'accountHash' => $oAccount->Hash(),
|
||||
'mainEmail' => \RainLoop\Api::Actions()->getMainAccountFromToken()->Email(),
|
||||
'contactsAllowed' => $this->AddressBookProvider($oAccount)->IsActive(),
|
||||
'HideUnsubscribed' => false,
|
||||
'UseThreads' => (bool) $oConfig->Get('defaults', 'mail_use_threads', false),
|
||||
'ReplySameFolder' => (bool) $oConfig->Get('defaults', 'mail_reply_same_folder', false),
|
||||
'HideDeleted' => true,
|
||||
'ShowUnreadCount' => false,
|
||||
'UnhideKolabFolders' => false,
|
||||
'CheckMailInterval' => 15
|
||||
];
|
||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||
if ($oSettingsLocal instanceof \RainLoop\Settings) {
|
||||
$aResult['SentFolder'] = (string) $oSettingsLocal->GetConf('SentFolder', '');
|
||||
$aResult['DraftsFolder'] = (string) $oSettingsLocal->GetConf('DraftsFolder', '');
|
||||
$aResult['JunkFolder'] = (string) $oSettingsLocal->GetConf('JunkFolder', '');
|
||||
$aResult['TrashFolder'] = (string) $oSettingsLocal->GetConf('TrashFolder', '');
|
||||
$aResult['ArchiveFolder'] = (string) $oSettingsLocal->GetConf('ArchiveFolder', '');
|
||||
$aResult['HideUnsubscribed'] = (bool) $oSettingsLocal->GetConf('HideUnsubscribed', $aResult['HideUnsubscribed']);
|
||||
$aResult['UseThreads'] = (bool) $oSettingsLocal->GetConf('UseThreads', $aResult['UseThreads']);
|
||||
$aResult['ReplySameFolder'] = (bool) $oSettingsLocal->GetConf('ReplySameFolder', $aResult['ReplySameFolder']);
|
||||
$aResult['HideDeleted'] = (bool)$oSettingsLocal->GetConf('HideDeleted', $aResult['HideDeleted']);
|
||||
$aResult['ShowUnreadCount'] = (bool)$oSettingsLocal->GetConf('ShowUnreadCount', $aResult['ShowUnreadCount']);
|
||||
$aResult['UnhideKolabFolders'] = (bool)$oSettingsLocal->GetConf('UnhideKolabFolders', $aResult['UnhideKolabFolders']);
|
||||
$aResult['CheckMailInterval'] = (int)$oSettingsLocal->GetConf('CheckMailInterval', $aResult['CheckMailInterval']);
|
||||
/*
|
||||
foreach ($oSettingsLocal->toArray() as $key => $value) {
|
||||
$aResult[\lcfirst($key)] = $value;
|
||||
}
|
||||
$aResult['junkFolder'] = $aResult['spamFolder'];
|
||||
unset($aResult['checkableFolder']);
|
||||
unset($aResult['theme']);
|
||||
*/
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -141,32 +243,11 @@ trait Accounts
|
|||
{
|
||||
if ($this->switchAccount(\trim($this->GetActionParam('Email', '')))) {
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
$aResult['Email'] = $oAccount->Email();
|
||||
$aResult['IncLogin'] = $oAccount->IncLogin();
|
||||
$aResult['OutLogin'] = $oAccount->OutLogin();
|
||||
$aResult['AccountHash'] = $oAccount->Hash();
|
||||
$aResult['MainEmail'] = ($oAccount instanceof AdditionalAccount)
|
||||
? $oAccount->ParentEmail() : '';
|
||||
$aResult['ContactsIsAllowed'] = $this->AddressBookProvider($oAccount)->IsActive();
|
||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||
if ($oSettingsLocal instanceof \RainLoop\Settings) {
|
||||
$oConfig = $this->Config();
|
||||
$aResult['SentFolder'] = (string) $oSettingsLocal->GetConf('SentFolder', '');
|
||||
$aResult['DraftsFolder'] = (string) $oSettingsLocal->GetConf('DraftFolder', '');
|
||||
$aResult['SpamFolder'] = (string) $oSettingsLocal->GetConf('SpamFolder', '');
|
||||
$aResult['TrashFolder'] = (string) $oSettingsLocal->GetConf('TrashFolder', '');
|
||||
$aResult['ArchiveFolder'] = (string) $oSettingsLocal->GetConf('ArchiveFolder', '');
|
||||
$aResult['HideUnsubscribed'] = (bool) $oSettingsLocal->GetConf('HideUnsubscribed', false);
|
||||
$aResult['UseThreads'] = (bool) $oSettingsLocal->GetConf('UseThreads', $oConfig->Get('defaults', 'mail_use_threads', false));
|
||||
$aResult['ReplySameFolder'] = (bool) $oSettingsLocal->GetConf('ReplySameFolder', $oConfig->Get('defaults', 'mail_reply_same_folder', false));
|
||||
$aResult['HideDeleted'] = (bool) $oSettingsLocal->GetConf('HideDeleted', true);
|
||||
$aResult['UnhideKolabFolders'] = (bool) $oSettingsLocal->GetConf('UnhideKolabFolders', false);
|
||||
}
|
||||
$aResult = $this->getAccountData($oAccount);
|
||||
// $this->Plugins()->InitAppData($bAdmin, $aResult, $oAccount);
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $aResult);
|
||||
return $this->DefaultResponse($aResult);
|
||||
}
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -182,7 +263,7 @@ trait Accounts
|
|||
}
|
||||
|
||||
$this->IdentitiesProvider()->UpdateIdentity($oAccount, $oIdentity);
|
||||
return $this->DefaultResponse(__FUNCTION__, true);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -193,16 +274,16 @@ trait Accounts
|
|||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
if (!$this->GetCapa(Capa::IDENTITIES)) {
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
$sId = \trim($this->GetActionParam('IdToDelete', ''));
|
||||
$sId = \trim($this->GetActionParam('idToDelete', ''));
|
||||
if (empty($sId)) {
|
||||
throw new ClientException(Notifications::UnknownError);
|
||||
}
|
||||
|
||||
$this->IdentitiesProvider()->DeleteIdentity($oAccount, $sId);
|
||||
return $this->DefaultResponse(__FUNCTION__, true);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -214,7 +295,7 @@ trait Accounts
|
|||
$aIdentities = $this->GetActionParam('Identities', null);
|
||||
|
||||
if (!\is_array($aAccounts) && !\is_array($aIdentities)) {
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
if (\is_array($aAccounts) && 1 < \count($aAccounts)) {
|
||||
|
|
@ -226,7 +307,7 @@ trait Accounts
|
|||
$this->SetAccounts($oAccount, $aAccounts);
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $this->LocalStorageProvider()->Put(
|
||||
return $this->DefaultResponse($this->LocalStorageProvider()->Put(
|
||||
$this->getAccountFromToken(),
|
||||
StorageType::CONFIG,
|
||||
'identities_order',
|
||||
|
|
@ -241,11 +322,16 @@ trait Accounts
|
|||
*/
|
||||
public function DoAccountsAndIdentities(): array
|
||||
{
|
||||
return $this->DefaultResponse(__FUNCTION__, array(
|
||||
'Accounts' => \array_map(
|
||||
'MailSo\\Base\\Utils::IdnToUtf8',
|
||||
\array_keys($this->GetAccounts($this->getMainAccountFromToken()))
|
||||
),
|
||||
// https://github.com/the-djmaze/snappymail/issues/571
|
||||
return $this->DefaultResponse(array(
|
||||
'Accounts' => \array_values(\array_map(function($value){
|
||||
return [
|
||||
'email' => \MailSo\Base\Utils::IdnToUtf8($value['email'] ?? $value[1]),
|
||||
'name' => $value['name'] ?? ''
|
||||
];
|
||||
},
|
||||
$this->GetAccounts($this->getMainAccountFromToken())
|
||||
)),
|
||||
'Identities' => $this->GetIdentities($this->getAccountFromToken())
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@ use RainLoop\KeyPathHelper;
|
|||
use RainLoop\Notifications;
|
||||
use RainLoop\Utils;
|
||||
|
||||
//define('APP_DEV_VERSION', '0.0.0');
|
||||
|
||||
trait Admin
|
||||
{
|
||||
protected static $AUTH_ADMIN_TOKEN_KEY = 'smadmin';
|
||||
protected static string $AUTH_ADMIN_TOKEN_KEY = 'smadmin';
|
||||
|
||||
public function IsAdminLoggined(bool $bThrowExceptionOnFalse = true) : bool
|
||||
{
|
||||
|
|
@ -22,8 +20,7 @@ trait Admin
|
|||
}
|
||||
}
|
||||
|
||||
if ($bThrowExceptionOnFalse)
|
||||
{
|
||||
if ($bThrowExceptionOnFalse) {
|
||||
throw new ClientException(Notifications::AuthError);
|
||||
}
|
||||
|
||||
|
|
@ -32,13 +29,13 @@ trait Admin
|
|||
|
||||
protected function getAdminAuthKey() : string
|
||||
{
|
||||
$cookie = Utils::GetCookie(static::$AUTH_ADMIN_TOKEN_KEY, '');
|
||||
$cookie = \SnappyMail\Cookies::get(static::$AUTH_ADMIN_TOKEN_KEY);
|
||||
if ($cookie) {
|
||||
$aAdminHash = Utils::DecodeKeyValuesQ($cookie);
|
||||
if (!empty($aAdminHash[1]) && 'token' === $aAdminHash[0]) {
|
||||
return $aAdminHash[1];
|
||||
}
|
||||
Utils::ClearCookie(static::$AUTH_ADMIN_TOKEN_KEY);
|
||||
\SnappyMail\Cookies::clear(static::$AUTH_ADMIN_TOKEN_KEY);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
namespace RainLoop\Actions;
|
||||
|
||||
use RainLoop\Enumerations\PluginPropertyType;
|
||||
use RainLoop\Exceptions\ClientException;
|
||||
|
||||
trait AdminDomains
|
||||
{
|
||||
public function DoAdminDomainLoad() : array
|
||||
{
|
||||
$this->IsAdminLoggined();
|
||||
|
||||
return $this->DefaultResponse($this->DomainProvider()->Load($this->GetActionParam('name', ''), false, false));
|
||||
}
|
||||
|
||||
public function DoAdminDomainList() : array
|
||||
{
|
||||
$this->IsAdminLoggined();
|
||||
$bIncludeAliases = !empty($this->GetActionParam('includeAliases', '1'));
|
||||
return $this->DefaultResponse($this->DomainProvider()->GetList($bIncludeAliases));
|
||||
}
|
||||
|
||||
public function DoAdminDomainDelete() : array
|
||||
{
|
||||
$this->IsAdminLoggined();
|
||||
|
||||
return $this->DefaultResponse($this->DomainProvider()->Delete((string) $this->GetActionParam('name', '')));
|
||||
}
|
||||
|
||||
public function DoAdminDomainDisable() : array
|
||||
{
|
||||
$this->IsAdminLoggined();
|
||||
|
||||
return $this->DefaultResponse($this->DomainProvider()->Disable(
|
||||
(string) $this->GetActionParam('name', ''),
|
||||
!empty($this->GetActionParam('disabled', '0'))
|
||||
));
|
||||
}
|
||||
|
||||
public function DoAdminDomainSave() : array
|
||||
{
|
||||
$this->IsAdminLoggined();
|
||||
|
||||
$oDomain = $this->DomainProvider()->LoadOrCreateNewFromAction($this);
|
||||
|
||||
return $this->DefaultResponse($oDomain ? $this->DomainProvider()->Save($oDomain) : false);
|
||||
}
|
||||
|
||||
public function DoAdminDomainAliasSave() : array
|
||||
{
|
||||
$this->IsAdminLoggined();
|
||||
|
||||
return $this->DefaultResponse($this->DomainProvider()->SaveAlias(
|
||||
(string) $this->GetActionParam('name', ''),
|
||||
(string) $this->GetActionParam('alias', '')
|
||||
));
|
||||
}
|
||||
|
||||
public function DoAdminDomainMatch() : array
|
||||
{
|
||||
$sEmail = $this->GetActionParam('username');
|
||||
$sPassword = '********';
|
||||
$sLogin = '';
|
||||
$this->resolveLoginCredentials($sEmail, $sPassword, $sLogin);
|
||||
$oDomain = \str_contains($sEmail, '@')
|
||||
? $this->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true)
|
||||
: null;
|
||||
return $this->DefaultResponse(array(
|
||||
'email' => $sEmail,
|
||||
'login' => $sLogin,
|
||||
'domain' => $oDomain,
|
||||
'whitelist' => $oDomain ? $oDomain->ValidateWhiteList($sEmail, $sLogin) : null
|
||||
));
|
||||
}
|
||||
|
||||
public function DoAdminDomainTest() : array
|
||||
{
|
||||
$this->IsAdminLoggined();
|
||||
|
||||
$bImapResult = false;
|
||||
$sImapErrorDesc = '';
|
||||
$bSmtpResult = false;
|
||||
$sSmtpErrorDesc = '';
|
||||
$bSieveResult = false;
|
||||
$sSieveErrorDesc = '';
|
||||
|
||||
$oDomain = $this->DomainProvider()->LoadOrCreateNewFromAction($this, 'test.example.com');
|
||||
if ($oDomain) {
|
||||
$aAuth = $this->GetActionParam('auth');
|
||||
|
||||
try
|
||||
{
|
||||
$oImapClient = new \MailSo\Imap\ImapClient();
|
||||
$oImapClient->SetLogger($this->Logger());
|
||||
|
||||
$oSettings = $oDomain->ImapSettings();
|
||||
$oImapClient->Connect($oSettings);
|
||||
|
||||
if (!empty($aAuth['user'])) {
|
||||
$oSettings->Login = $aAuth['user'];
|
||||
$oSettings->Password = $aAuth['pass'];
|
||||
$oImapClient->Login($oSettings);
|
||||
}
|
||||
|
||||
$oImapClient->Disconnect();
|
||||
$bImapResult = true;
|
||||
}
|
||||
catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException)
|
||||
{
|
||||
$this->logException($oException, \LOG_ERR);
|
||||
$sImapErrorDesc = $oException->getSocketMessage();
|
||||
if (empty($sImapErrorDesc)) {
|
||||
$sImapErrorDesc = $oException->getMessage();
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$this->logException($oException, \LOG_ERR);
|
||||
$sImapErrorDesc = $oException->getMessage();
|
||||
}
|
||||
|
||||
if ($oDomain->OutUsePhpMail()) {
|
||||
$bSmtpResult = \MailSo\Base\Utils::FunctionCallable('mail');
|
||||
if (!$bSmtpResult) {
|
||||
$sSmtpErrorDesc = 'PHP: mail() function is undefined';
|
||||
}
|
||||
} else {
|
||||
try
|
||||
{
|
||||
$oSmtpClient = new \MailSo\Smtp\SmtpClient();
|
||||
$oSmtpClient->SetLogger($this->Logger());
|
||||
|
||||
$oSettings = $oDomain->SmtpSettings();
|
||||
$oSettings->Ehlo = \MailSo\Smtp\SmtpClient::EhloHelper();
|
||||
$oSmtpClient->Connect($oSettings);
|
||||
|
||||
if (!empty($aAuth['user'])) {
|
||||
$oSettings->Login = $aAuth['user'];
|
||||
$oSettings->Password = $aAuth['pass'];
|
||||
$oSmtpClient->Login($oSettings);
|
||||
}
|
||||
|
||||
$oSmtpClient->Disconnect();
|
||||
$bSmtpResult = true;
|
||||
}
|
||||
catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException)
|
||||
{
|
||||
$this->logException($oException, \LOG_ERR);
|
||||
$sSmtpErrorDesc = $oException->getSocketMessage();
|
||||
if (empty($sSmtpErrorDesc)) {
|
||||
$sSmtpErrorDesc = $oException->getMessage();
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$this->logException($oException, \LOG_ERR);
|
||||
$sSmtpErrorDesc = $oException->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if ($oDomain->UseSieve()) {
|
||||
try
|
||||
{
|
||||
$oSieveClient = new \MailSo\Sieve\SieveClient();
|
||||
$oSieveClient->SetLogger($this->Logger());
|
||||
|
||||
$oSettings = $oDomain->SieveSettings();
|
||||
$oSieveClient->Connect($oSettings);
|
||||
|
||||
if (!empty($aAuth['user'])) {
|
||||
$oSettings->Login = $aAuth['user'];
|
||||
$oSettings->Password = $aAuth['pass'];
|
||||
$oSieveClient->Login($oSettings);
|
||||
}
|
||||
|
||||
$oSieveClient->Disconnect();
|
||||
$bSieveResult = true;
|
||||
}
|
||||
catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException)
|
||||
{
|
||||
$this->logException($oException, \LOG_ERR);
|
||||
$sSieveErrorDesc = $oException->getSocketMessage();
|
||||
if (empty($sSieveErrorDesc)) {
|
||||
$sSieveErrorDesc = $oException->getMessage();
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$this->logException($oException, \LOG_ERR);
|
||||
$sSieveErrorDesc = $oException->getMessage();
|
||||
}
|
||||
} else {
|
||||
$bSieveResult = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(array(
|
||||
'Imap' => $bImapResult ? true : $sImapErrorDesc,
|
||||
'Smtp' => $bSmtpResult ? true : $sSmtpErrorDesc,
|
||||
'Sieve' => $bSieveResult ? true : $sSieveErrorDesc
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
namespace RainLoop\Actions;
|
||||
|
||||
use RainLoop\Enumerations\PluginPropertyType;
|
||||
use RainLoop\Exceptions\ClientException;
|
||||
use RainLoop\Notifications;
|
||||
use SnappyMail\Repository;
|
||||
|
||||
trait AdminExtensions
|
||||
{
|
||||
|
||||
public function DoAdminPackagesList() : array
|
||||
{
|
||||
return $this->DefaultResponse(Repository::getPackagesList());
|
||||
}
|
||||
|
||||
public function DoAdminPackageDelete() : array
|
||||
{
|
||||
return $this->DefaultResponse(Repository::deletePackage($this->GetActionParam('id', '')));
|
||||
}
|
||||
|
||||
public function DoAdminPackageInstall() : array
|
||||
{
|
||||
$sType = $this->GetActionParam('type', '');
|
||||
$bResult = Repository::installPackage(
|
||||
$sType,
|
||||
$this->GetActionParam('id', ''),
|
||||
$this->GetActionParam('file', '')
|
||||
);
|
||||
return $this->DefaultResponse($bResult ?
|
||||
('plugin' !== $sType ? array('Reload' => true) : true) : false);
|
||||
}
|
||||
|
||||
public function DoAdminPluginDisable() : array
|
||||
{
|
||||
$this->IsAdminLoggined();
|
||||
|
||||
$sId = (string) $this->GetActionParam('id', '');
|
||||
$bDisable = '1' === (string) $this->GetActionParam('disabled', '1');
|
||||
|
||||
if (!$bDisable) {
|
||||
$oPlugin = $this->Plugins()->CreatePluginByName($sId);
|
||||
if ($oPlugin) {
|
||||
$sValue = $oPlugin->Supported();
|
||||
if (\strlen($sValue)) {
|
||||
return $this->FalseResponse(Notifications::UnsupportedPluginPackage, $sValue);
|
||||
}
|
||||
} else {
|
||||
return $this->FalseResponse(Notifications::InvalidPluginPackage);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(Repository::enablePackage($sId, !$bDisable));
|
||||
}
|
||||
|
||||
public function DoAdminPluginLoad() : array
|
||||
{
|
||||
$this->IsAdminLoggined();
|
||||
|
||||
$mResult = false;
|
||||
$sId = (string) $this->GetActionParam('id', '');
|
||||
|
||||
if (!empty($sId)) {
|
||||
$oPlugin = $this->Plugins()->CreatePluginByName($sId);
|
||||
if ($oPlugin) {
|
||||
$mResult = array(
|
||||
'@Object' => 'Object/Plugin',
|
||||
'id' => $sId,
|
||||
'name' => $oPlugin->Name(),
|
||||
'readme' => $oPlugin->Description(),
|
||||
'config' => array()
|
||||
);
|
||||
|
||||
$aMap = $oPlugin->ConfigMap();
|
||||
if (\is_array($aMap)) {
|
||||
$oConfig = $oPlugin->Config();
|
||||
foreach ($aMap as $oItem) {
|
||||
if ($oItem) {
|
||||
if ($oItem instanceof \RainLoop\Plugins\Property) {
|
||||
if (PluginPropertyType::PASSWORD === $oItem->Type()) {
|
||||
$oItem->SetValue(static::APP_DUMMY);
|
||||
} else {
|
||||
$oItem->SetValue($oConfig->Get('plugin', $oItem->Name(), ''));
|
||||
}
|
||||
$mResult['config'][] = $oItem;
|
||||
} else if ($oItem instanceof \RainLoop\Plugins\PropertyCollection) {
|
||||
foreach ($oItem as $oSubItem) {
|
||||
if ($oSubItem && $oSubItem instanceof \RainLoop\Plugins\Property) {
|
||||
if (PluginPropertyType::PASSWORD === $oSubItem->Type()) {
|
||||
$oSubItem->SetValue(static::APP_DUMMY);
|
||||
} else {
|
||||
$oSubItem->SetValue($oConfig->Get('plugin', $oSubItem->Name(), ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
$mResult['config'][] = $oItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->DefaultResponse($mResult);
|
||||
}
|
||||
|
||||
public function DoAdminPluginSettingsUpdate() : array
|
||||
{
|
||||
$this->IsAdminLoggined();
|
||||
|
||||
$sId = (string) $this->GetActionParam('id', '');
|
||||
|
||||
if (!empty($sId)) {
|
||||
$oPlugin = $this->Plugins()->CreatePluginByName($sId);
|
||||
if ($oPlugin) {
|
||||
$oConfig = $oPlugin->Config();
|
||||
$aMap = $oPlugin->ConfigMap(true);
|
||||
if (\is_array($aMap)) {
|
||||
$aSettings = (array) $this->GetActionParam('settings', []);
|
||||
foreach ($aMap as $oItem) {
|
||||
$sKey = $oItem->Name();
|
||||
$mValue = $aSettings[$sKey] ?? $oConfig->Get('plugin', $sKey);
|
||||
if (PluginPropertyType::PASSWORD !== $oItem->Type() || static::APP_DUMMY !== $mValue) {
|
||||
$oItem->SetValue($mValue);
|
||||
$mValue = $oItem->Value();
|
||||
if (null !== $mValue) {
|
||||
if ($oItem->encrypted) {
|
||||
$oConfig->setEncrypted('plugin', $sKey, $mValue);
|
||||
} else {
|
||||
$oConfig->Set('plugin', $sKey, $mValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($oConfig->Save()) {
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new ClientException(Notifications::CantSavePluginSettings);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
<?php
|
||||
|
||||
namespace RainLoop\Actions;
|
||||
|
||||
use RainLoop\Enumerations\Capa;
|
||||
use RainLoop\Utils;
|
||||
|
||||
trait Attachments
|
||||
{
|
||||
/**
|
||||
* @throws \MailSo\RuntimeException
|
||||
*/
|
||||
public function DoAttachmentsActions() : array
|
||||
{
|
||||
$sAction = $this->GetActionParam('target', '');
|
||||
$sFolder = $this->GetActionParam('folder', '');
|
||||
$sFilename = \MailSo\Base\Utils::SecureFileName($this->GetActionParam('filename', ''));
|
||||
$aHashes = $this->GetActionParam('hashes', null);
|
||||
$oFilesProvider = $this->FilesProvider();
|
||||
if (empty($sAction) || !$this->GetCapa(Capa::ATTACHMENTS_ACTIONS) || !$oFilesProvider || !$oFilesProvider->IsActive()) {
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
$oAccount = $this->initMailClientConnection();
|
||||
|
||||
$bError = false;
|
||||
$aData = [];
|
||||
$mUIDs = [];
|
||||
|
||||
if (\is_array($aHashes) && \count($aHashes)) {
|
||||
foreach ($aHashes as $sZipHash) {
|
||||
$aResult = $this->getMimeFileByHash($oAccount, $sZipHash);
|
||||
if (empty($aResult['fileHash'])) {
|
||||
$bError = true;
|
||||
break;
|
||||
}
|
||||
$aData[] = $aResult;
|
||||
if (!empty($aResult['mimeIndex'])) {
|
||||
$mUIDs[$aResult['uid']] = $aResult['uid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$mUIDs = 1 < \count($mUIDs);
|
||||
|
||||
if ($bError || !\count($aData)) {
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
$mResult = false;
|
||||
switch (\strtolower($sAction))
|
||||
{
|
||||
case 'zip':
|
||||
|
||||
$sZipHash = \MailSo\Base\Utils::Sha1Rand();
|
||||
$sZipFileName = $oFilesProvider->GenerateLocalFullFileName($oAccount, $sZipHash);
|
||||
|
||||
if (!empty($sZipFileName)) {
|
||||
if (\class_exists('ZipArchive')) {
|
||||
$oZip = new \ZipArchive();
|
||||
$oZip->open($sZipFileName, \ZIPARCHIVE::CREATE | \ZIPARCHIVE::OVERWRITE);
|
||||
$oZip->setArchiveComment('SnappyMail/'.APP_VERSION);
|
||||
foreach ($aData as $aItem) {
|
||||
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $aItem['fileHash']);
|
||||
$sFileName = ($mUIDs ? "{$aItem['uid']}/" : ($sFolder ? "{$aItem['uid']}-" : '')) . $aItem['fileName'];
|
||||
if (!$oZip->addFile($sFullFileNameHash, $sFileName)) {
|
||||
$bError = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bError) {
|
||||
$oZip->close();
|
||||
} else {
|
||||
$bError = !$oZip->close();
|
||||
}
|
||||
/*
|
||||
} else {
|
||||
@\unlink($sZipFileName);
|
||||
$oZip = new \SnappyMail\Stream\ZIP($sZipFileName);
|
||||
// $oZip->setArchiveComment('SnappyMail/'.APP_VERSION);
|
||||
foreach ($aData as $aItem) {
|
||||
if ($aItem['fileHash']) {
|
||||
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $aItem['fileHash']);
|
||||
if (!$oZip->addFile($sFullFileNameHash, $aItem['fileName'])) {
|
||||
$bError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$oZip->close();
|
||||
*/
|
||||
} else {
|
||||
@\unlink($sZipFileName);
|
||||
$oZip = new \PharData($sZipFileName . '.zip', 0, null, \Phar::ZIP);
|
||||
$oZip->compressFiles(\Phar::GZ);
|
||||
foreach ($aData as $aItem) {
|
||||
$oZip->addFile(
|
||||
$oFilesProvider->GetFileName($oAccount, $aItem['fileHash']),
|
||||
($mUIDs ? "{$aItem['uid']}/" : ($sFolder ? "{$aItem['uid']}-" : '')) . $aItem['fileName']
|
||||
);
|
||||
}
|
||||
$oZip->compressFiles(\Phar::GZ);
|
||||
unset($oZip);
|
||||
\rename($sZipFileName . '.zip', $sZipFileName);
|
||||
}
|
||||
|
||||
foreach ($aData as $aItem) {
|
||||
$oFilesProvider->Clear($oAccount, $aItem['fileHash']);
|
||||
}
|
||||
|
||||
if (!$bError) {
|
||||
$mResult = array(
|
||||
'fileHash' => $this->encodeRawKey(array(
|
||||
'fileName' => ($sFilename ?: ($sFolder ? 'messages' : 'attachments')) . \date('-YmdHis') . '.zip',
|
||||
'mimeType' => 'application/zip',
|
||||
'fileHash' => $sZipHash
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$data = new \SnappyMail\AttachmentsAction;
|
||||
$data->action = $sAction;
|
||||
$data->items = $aData;
|
||||
$data->filesProvider = $oFilesProvider;
|
||||
$data->account = $oAccount;
|
||||
$this->Plugins()->RunHook('json.attachments', array($data));
|
||||
$mResult = $data->result;
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->DefaultResponse($bError ? false : $mResult);
|
||||
}
|
||||
|
||||
private function getMimeFileByHash(\RainLoop\Model\Account $oAccount, string $sHash) : array
|
||||
{
|
||||
$aValues = $this->decodeRawKey($sHash);
|
||||
|
||||
$sFolder = isset($aValues['folder']) ? (string) $aValues['folder'] : '';
|
||||
$iUid = isset($aValues['uid']) ? (int) $aValues['uid'] : 0;
|
||||
$sMimeIndex = isset($aValues['mimeIndex']) ? (string) $aValues['mimeIndex'] : '';
|
||||
|
||||
$sContentTypeIn = isset($aValues['mimeType']) ? (string) $aValues['mimeType'] : '';
|
||||
$sFileNameIn = isset($aValues['fileName']) ? (string) $aValues['fileName'] : 'file.dat';
|
||||
|
||||
$oFileProvider = $this->FilesProvider();
|
||||
|
||||
$sResultHash = '';
|
||||
|
||||
$mResult = $this->MailClient()->MessageMimeStream(function ($rResource, $sContentType, $sFileName, $sMimeIndex = '')
|
||||
use ($oAccount, $oFileProvider, $sFileNameIn, $sContentTypeIn, &$sResultHash) {
|
||||
|
||||
unset($sContentType, $sFileName, $sMimeIndex);
|
||||
|
||||
if (\is_resource($rResource)) {
|
||||
$sHash = \MailSo\Base\Utils::Sha1Rand($sFileNameIn.'~'.$sContentTypeIn);
|
||||
$rTempResource = $oFileProvider->GetFile($oAccount, $sHash, 'wb+');
|
||||
|
||||
if (\is_resource($rTempResource)) {
|
||||
if (-1 < \MailSo\Base\Utils::WriteStream($rResource, $rTempResource)) {
|
||||
$sResultHash = $sHash;
|
||||
}
|
||||
|
||||
\fclose($rTempResource);
|
||||
}
|
||||
}
|
||||
|
||||
}, $sFolder, $iUid, $sMimeIndex);
|
||||
|
||||
$aValues['fileName'] = $sFileNameIn;
|
||||
$aValues['fileHash'] = $mResult ? $sResultHash : '';
|
||||
|
||||
return $aValues;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,9 +10,8 @@ trait Contacts
|
|||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
||||
if (!$oAddressBookProvider || !$oAddressBookProvider->IsActive())
|
||||
{
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
if (!$oAddressBookProvider || !$oAddressBookProvider->IsActive()) {
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
$sPassword = $this->GetActionParam('Password', '');
|
||||
|
|
@ -22,12 +21,13 @@ trait Contacts
|
|||
$bResult = $this->setContactsSyncData($oAccount, array(
|
||||
'Mode' => \intval($this->GetActionParam('Mode', '0')),
|
||||
'User' => $this->GetActionParam('User', ''),
|
||||
'Password' => APP_DUMMY === $sPassword && isset($mData['Password'])
|
||||
? $mData['Password'] : (APP_DUMMY === $sPassword ? '' : $sPassword),
|
||||
'Password' => static::APP_DUMMY === $sPassword
|
||||
? (isset($mData['Password']) ? $mData['Password'] : '')
|
||||
: $sPassword,
|
||||
'Url' => $this->GetActionParam('Url', '')
|
||||
));
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $bResult);
|
||||
return $this->DefaultResponse($bResult);
|
||||
}
|
||||
|
||||
public function DoContactsSync() : array
|
||||
|
|
@ -35,15 +35,15 @@ trait Contacts
|
|||
$oAccount = $this->getAccountFromToken();
|
||||
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
||||
if (!$oAddressBookProvider) {
|
||||
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ContactsSyncError);
|
||||
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ContactsSyncError, null, 'No AddressBookProvider');
|
||||
}
|
||||
\ignore_user_abort(true);
|
||||
\SnappyMail\HTTP\Stream::start(/*$binary = false*/);
|
||||
\SnappyMail\HTTP\Stream::JSON(['messsage'=>'start']);
|
||||
if (!$oAddressBookProvider->Sync()) {
|
||||
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ContactsSyncError);
|
||||
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ContactsSyncError, null, 'AddressBookProvider->Sync() failed');
|
||||
}
|
||||
return $this->TrueResponse(__FUNCTION__);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
|
||||
public function DoContacts() : array
|
||||
|
|
@ -60,13 +60,12 @@ trait Contacts
|
|||
$mResult = array();
|
||||
|
||||
$oAbp = $this->AddressBookProvider($oAccount);
|
||||
if ($oAbp->IsActive())
|
||||
{
|
||||
if ($oAbp->IsActive()) {
|
||||
$iResultCount = 0;
|
||||
$mResult = $oAbp->GetContacts($iOffset, $iLimit, $sSearch, $iResultCount);
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, array(
|
||||
return $this->DefaultResponse(array(
|
||||
'Offset' => $iOffset,
|
||||
'Limit' => $iLimit,
|
||||
'Count' => $iResultCount,
|
||||
|
|
@ -78,17 +77,16 @@ trait Contacts
|
|||
public function DoContactsDelete() : array
|
||||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
$aUids = \explode(',', (string) $this->GetActionParam('Uids', ''));
|
||||
$aUids = \explode(',', (string) $this->GetActionParam('uids', ''));
|
||||
|
||||
$aFilteredUids = \array_filter(\array_map('intval', $aUids));
|
||||
|
||||
$bResult = false;
|
||||
if (\count($aFilteredUids) && $this->AddressBookProvider($oAccount)->IsActive())
|
||||
{
|
||||
if (\count($aFilteredUids) && $this->AddressBookProvider($oAccount)->IsActive()) {
|
||||
$bResult = $this->AddressBookProvider($oAccount)->DeleteContacts($aFilteredUids);
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $bResult);
|
||||
return $this->DefaultResponse($bResult);
|
||||
}
|
||||
|
||||
public function DoContactSave() : array
|
||||
|
|
@ -97,14 +95,14 @@ trait Contacts
|
|||
|
||||
$bResult = false;
|
||||
|
||||
if ($this->HasActionParam('Uid') && $this->HasActionParam('jCard')) {
|
||||
if ($this->HasActionParam('uid') && $this->HasActionParam('jCard')) {
|
||||
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
||||
if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) {
|
||||
$vCard = \Sabre\VObject\Reader::readJson($this->GetActionParam('jCard'));
|
||||
if ($vCard && $vCard instanceof \Sabre\VObject\Component\VCard) {
|
||||
$vCard->REV = \gmdate('Ymd\\THis\\Z');
|
||||
$vCard->PRODID = 'SnappyMail-'.APP_VERSION;
|
||||
$sUid = \trim($this->GetActionParam('Uid'));
|
||||
$sUid = \trim($this->GetActionParam('uid'));
|
||||
$oContact = $sUid ? $oAddressBookProvider->GetContactByID($sUid) : null;
|
||||
if (!$oContact) {
|
||||
$oContact = new \RainLoop\Providers\AddressBook\Classes\Contact();
|
||||
|
|
@ -115,75 +113,58 @@ trait Contacts
|
|||
}
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, array(
|
||||
return $this->DefaultResponse(array(
|
||||
'ResultID' => $bResult ? $oContact->id : '',
|
||||
'Result' => $bResult
|
||||
));
|
||||
}
|
||||
|
||||
public function UploadContacts() : array
|
||||
public function UploadContacts(?array $aFile, int $iError) : array
|
||||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
$mResponse = false;
|
||||
|
||||
$aFile = $this->GetActionParam('File', null);
|
||||
$iError = $this->GetActionParam('Error', \RainLoop\Enumerations\UploadError::UNKNOWN);
|
||||
|
||||
if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile))
|
||||
{
|
||||
if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) {
|
||||
$sSavedName = 'upload-post-'.\md5($aFile['name'].$aFile['tmp_name']);
|
||||
if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name']))
|
||||
{
|
||||
if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) {
|
||||
$iError = \RainLoop\Enumerations\UploadError::ON_SAVING;
|
||||
}
|
||||
else
|
||||
{
|
||||
\ini_set('auto_detect_line_endings', true);
|
||||
} else {
|
||||
\ini_set('auto_detect_line_endings', '1');
|
||||
$mData = $this->FilesProvider()->GetFile($oAccount, $sSavedName);
|
||||
if ($mData)
|
||||
{
|
||||
$sFileStart = \fread($mData, 20);
|
||||
if ($mData) {
|
||||
$sFileStart = \fread($mData, 128);
|
||||
\rewind($mData);
|
||||
|
||||
if (false !== $sFileStart)
|
||||
{
|
||||
if (false !== $sFileStart) {
|
||||
$sFileStart = \trim($sFileStart);
|
||||
if (false !== \strpos($sFileStart, 'BEGIN:VCARD'))
|
||||
{
|
||||
if (false !== \strpos($sFileStart, 'BEGIN:VCARD')) {
|
||||
$mResponse = $this->importContactsFromVcfFile($oAccount, $mData);
|
||||
}
|
||||
else if (false !== \strpos($sFileStart, ',') || false !== \strpos($sFileStart, ';'))
|
||||
{
|
||||
} else if (false !== \strpos($sFileStart, ',') || false !== \strpos($sFileStart, ';')) {
|
||||
$mResponse = $this->importContactsFromCsvFile($oAccount, $mData, $sFileStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_resource($mData))
|
||||
{
|
||||
if (\is_resource($mData)) {
|
||||
\fclose($mData);
|
||||
}
|
||||
|
||||
unset($mData);
|
||||
$this->FilesProvider()->Clear($oAccount, $sSavedName);
|
||||
|
||||
\ini_set('auto_detect_line_endings', false);
|
||||
\ini_set('auto_detect_line_endings', '0');
|
||||
}
|
||||
}
|
||||
|
||||
if (UPLOAD_ERR_OK !== $iError)
|
||||
{
|
||||
$iClientError = \RainLoop\Enumerations\UploadError::NORMAL;
|
||||
$sError = $this->getUploadErrorMessageByCode($iError, $iClientError);
|
||||
|
||||
if (!empty($sError))
|
||||
{
|
||||
return $this->FalseResponse(__FUNCTION__, $iClientError, $sError);
|
||||
if (UPLOAD_ERR_OK !== $iError) {
|
||||
$iClientError = 0;
|
||||
$sError = \RainLoop\Enumerations\UploadError::getUserMessage($iError, $iClientError);
|
||||
if (!empty($sError)) {
|
||||
return $this->FalseResponse($iClientError, $sError);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $mResponse);
|
||||
return $this->DefaultResponse($mResponse);
|
||||
}
|
||||
|
||||
public function setContactsSyncData(\RainLoop\Model\Account $oAccount, array $aData) : bool
|
||||
|
|
@ -273,64 +254,45 @@ 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()) {
|
||||
try
|
||||
{
|
||||
$this->logWrite('Import contacts from vcf');
|
||||
foreach (\RainLoop\Providers\AddressBook\Utils::VcfStreamToContacts($rFile) as $oContact) {
|
||||
if ($oAddressBookProvider->ContactSave($oContact)) {
|
||||
++$iCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oExc)
|
||||
{
|
||||
$this->logException($oExc);
|
||||
}
|
||||
}
|
||||
|
||||
return $iCount;
|
||||
}
|
||||
|
||||
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()) {
|
||||
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
||||
if (\is_resource($rFile) && $oAddressBookProvider && $oAddressBookProvider->IsActive()) {
|
||||
try
|
||||
{
|
||||
$this->logWrite('Import contacts from csv');
|
||||
$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;
|
||||
foreach (\RainLoop\Providers\AddressBook\Utils::CsvStreamToContacts($rFile, $sDelimiter) as $oContact) {
|
||||
if ($oAddressBookProvider->ContactSave($oContact)) {
|
||||
++$iCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (\count($aData)) {
|
||||
$this->oLogger->Write('Import contacts from csv');
|
||||
$iCount = $oAddressBookProvider->ImportCsvArray($aData);
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oExc)
|
||||
{
|
||||
$this->logException($oExc);
|
||||
}
|
||||
}
|
||||
|
||||
return $iCount;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@ use RainLoop\Enumerations\Capa;
|
|||
|
||||
trait Filters
|
||||
{
|
||||
/**
|
||||
* @var \RainLoop\Providers\Filters
|
||||
*/
|
||||
private $oFiltersProvider;
|
||||
private ?\RainLoop\Providers\Filters $oFiltersProvider = null;
|
||||
|
||||
/**
|
||||
* @throws \MailSo\RuntimeException
|
||||
|
|
@ -18,12 +15,11 @@ trait Filters
|
|||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
if (!$this->GetCapa(Capa::SIEVE, $oAccount))
|
||||
{
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
if (!$this->GetCapa(Capa::SIEVE, $oAccount)) {
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Load($oAccount));
|
||||
return $this->DefaultResponse($this->FiltersProvider()->Load($oAccount));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -34,7 +30,7 @@ trait Filters
|
|||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
if (!$this->GetCapa(Capa::SIEVE, $oAccount)) {
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
$sName = $this->GetActionParam('name', '');
|
||||
|
|
@ -43,7 +39,7 @@ trait Filters
|
|||
// $this->FiltersProvider()->ActivateScript($oAccount, $sName);
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Save(
|
||||
return $this->DefaultResponse($this->FiltersProvider()->Save(
|
||||
$oAccount, $sName, $this->GetActionParam('body', '')
|
||||
));
|
||||
}
|
||||
|
|
@ -56,10 +52,10 @@ trait Filters
|
|||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
if (!$this->GetCapa(Capa::SIEVE, $oAccount)) {
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->ActivateScript(
|
||||
return $this->DefaultResponse($this->FiltersProvider()->ActivateScript(
|
||||
$oAccount, $this->GetActionParam('name', '')
|
||||
));
|
||||
}
|
||||
|
|
@ -72,10 +68,10 @@ trait Filters
|
|||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
if (!$this->GetCapa(Capa::SIEVE, $oAccount)) {
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->DeleteScript(
|
||||
return $this->DefaultResponse($this->FiltersProvider()->DeleteScript(
|
||||
$oAccount, $this->GetActionParam('name', '')
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,44 +10,39 @@ use MailSo\Imap\Enumerations\FolderType;
|
|||
trait Folders
|
||||
{
|
||||
|
||||
private function getFolderCollection(bool $HideUnsubscribed) : ?\MailSo\Mail\FolderCollection
|
||||
{
|
||||
return $this->MailClient()->Folders('', '*',
|
||||
$HideUnsubscribed,
|
||||
(int) $this->Config()->Get('imap', 'folder_list_limit', 200),
|
||||
(bool) $this->Config()->Get('imap', 'use_list_status', true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends uploaded rfc822 message to mailbox
|
||||
* @throws \MailSo\RuntimeException
|
||||
*/
|
||||
public function Append(): bool
|
||||
public function DoFolderAppend(): array
|
||||
{
|
||||
$oAccount = $this->initMailClientConnection();
|
||||
|
||||
$sFolderFullName = $this->GetActionParam('Folder', '');
|
||||
$sFolderFullName = $this->GetActionParam('folder', '');
|
||||
|
||||
if ($oAccount
|
||||
&& !empty($sFolderFullName)
|
||||
&& !empty($_FILES['AppendFile'])
|
||||
&& \is_uploaded_file($_FILES['AppendFile']['tmp_name'])
|
||||
&& \UPLOAD_ERR_OK == $_FILES['AppendFile']['error']
|
||||
&& $this->oConfig->Get('labs', 'allow_message_append', false)
|
||||
) {
|
||||
$sSavedName = 'append-post-' . \md5($sFolderFullName . $_FILES['AppendFile']['name'] . $_FILES['AppendFile']['tmp_name']);
|
||||
if ($this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $_FILES['AppendFile']['tmp_name'])) {
|
||||
$iMessageStreamSize = $this->FilesProvider()->FileSize($oAccount, $sSavedName);
|
||||
$rMessageStream = $this->FilesProvider()->GetFile($oAccount, $sSavedName);
|
||||
|
||||
$this->MailClient()->MessageAppendStream($rMessageStream, $iMessageStreamSize, $sFolderFullName);
|
||||
|
||||
$this->FilesProvider()->Clear($oAccount, $sSavedName);
|
||||
}
|
||||
if (!$this->oConfig->Get('labs', 'allow_message_append', false)) {
|
||||
return $this->FalseResponse(999, 'Permission denied');
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, true);
|
||||
if (empty($_FILES['appendFile'])) {
|
||||
return $this->FalseResponse(999, 'No file');
|
||||
}
|
||||
|
||||
if (\UPLOAD_ERR_OK != $_FILES['appendFile']['error']) {
|
||||
return $this->FalseResponse($iErrorCode, \RainLoop\Enumerations\UploadError::getMessage($iErrorCode));
|
||||
}
|
||||
|
||||
if ($oAccount && !empty($sFolderFullName) && \is_uploaded_file($_FILES['appendFile']['tmp_name'])) {
|
||||
$sSavedName = 'append-post-' . \md5($sFolderFullName . $_FILES['appendFile']['name'] . $_FILES['appendFile']['tmp_name']);
|
||||
if ($this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $_FILES['appendFile']['tmp_name'])) {
|
||||
$iMessageStreamSize = $this->FilesProvider()->FileSize($oAccount, $sSavedName);
|
||||
$rMessageStream = $this->FilesProvider()->GetFile($oAccount, $sSavedName);
|
||||
$this->ImapClient()->MessageAppendStream($sFolderFullName, $rMessageStream, $iMessageStreamSize);
|
||||
$this->FilesProvider()->Clear($oAccount, $sSavedName);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
}
|
||||
return $this->FalseResponse(999);
|
||||
}
|
||||
|
||||
public function DoFolders() : array
|
||||
|
|
@ -60,159 +55,35 @@ trait Folders
|
|||
$HideUnsubscribed = (bool) $oSettingsLocal->GetConf('HideUnsubscribed', $HideUnsubscribed);
|
||||
}
|
||||
|
||||
$oFolderCollection = $this->getFolderCollection($HideUnsubscribed);
|
||||
$oFolderCollection = $this->MailClient()->Folders('', '*', $HideUnsubscribed);
|
||||
|
||||
if ($oFolderCollection)
|
||||
{
|
||||
$sNamespace = $this->MailClient()->GetPersonalNamespace();
|
||||
|
||||
$this->Plugins()->RunHook('filter.folders-post', array($oAccount, $oFolderCollection));
|
||||
|
||||
$aSystemFolders = array();
|
||||
$this->recFoldersTypes($oAccount, $oFolderCollection, $aSystemFolders);
|
||||
|
||||
if (!$this->Config()->Get('imap', 'use_sort', true)) {
|
||||
$oFolderCollection->capabilities = \array_filter($oFolderCollection->capabilities, function($item){
|
||||
return !\preg_match('/^E?SORT/', $item);
|
||||
});
|
||||
}
|
||||
|
||||
if ($this->Config()->Get('labs', 'autocreate_system_folders', false))
|
||||
{
|
||||
$bDoItAgain = false;
|
||||
|
||||
$sParent = \substr($sNamespace, 0, -1);
|
||||
|
||||
$sDelimiter = $oFolderCollection->FindDelimiter();
|
||||
|
||||
$aList = array();
|
||||
$aMap = $this->systemFoldersNames($oAccount);
|
||||
|
||||
if ('' === $oSettingsLocal->GetConf('SentFolder', ''))
|
||||
{
|
||||
$aList[] = FolderType::SENT;
|
||||
}
|
||||
|
||||
if ('' === $oSettingsLocal->GetConf('DraftFolder', ''))
|
||||
{
|
||||
$aList[] = FolderType::DRAFTS;
|
||||
}
|
||||
|
||||
if ('' === $oSettingsLocal->GetConf('SpamFolder', ''))
|
||||
{
|
||||
$aList[] = FolderType::JUNK;
|
||||
}
|
||||
|
||||
if ('' === $oSettingsLocal->GetConf('TrashFolder', ''))
|
||||
{
|
||||
$aList[] = FolderType::TRASH;
|
||||
}
|
||||
|
||||
if ('' === $oSettingsLocal->GetConf('ArchiveFolder', ''))
|
||||
{
|
||||
$aList[] = FolderType::ARCHIVE;
|
||||
}
|
||||
|
||||
$this->Plugins()->RunHook('filter.folders-system-types', array($oAccount, &$aList));
|
||||
|
||||
foreach ($aList as $iType)
|
||||
{
|
||||
if (!isset($aSystemFolders[$iType]))
|
||||
{
|
||||
$mFolderNameToCreate = \array_search($iType, $aMap);
|
||||
if (!empty($mFolderNameToCreate))
|
||||
{
|
||||
$iPos = \strrpos($mFolderNameToCreate, $sDelimiter);
|
||||
if (false !== $iPos)
|
||||
{
|
||||
$mNewParent = \substr($mFolderNameToCreate, 0, $iPos);
|
||||
$mNewFolderNameToCreate = \substr($mFolderNameToCreate, $iPos + 1);
|
||||
if (\strlen($mNewFolderNameToCreate))
|
||||
{
|
||||
$mFolderNameToCreate = $mNewFolderNameToCreate;
|
||||
}
|
||||
|
||||
if (\strlen($mNewParent))
|
||||
{
|
||||
$sParent = \strlen($sParent) ? $sParent.$sDelimiter.$mNewParent : $mNewParent;
|
||||
}
|
||||
}
|
||||
|
||||
$sFullNameToCheck = $mFolderNameToCreate;
|
||||
if (\strlen($sParent))
|
||||
{
|
||||
$sFullNameToCheck = $sParent.$sDelimiter.$sFullNameToCheck;
|
||||
}
|
||||
|
||||
if (!isset($oFolderCollection[$sFullNameToCheck]))
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->MailClient()->FolderCreate($mFolderNameToCreate, $sParent, true, $sDelimiter);
|
||||
$bDoItAgain = true;
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$this->Logger()->WriteException($oException);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($bDoItAgain)
|
||||
{
|
||||
$oFolderCollection = $this->getFolderCollection($HideUnsubscribed);
|
||||
|
||||
if ($oFolderCollection)
|
||||
{
|
||||
$aSystemFolders = array();
|
||||
$this->recFoldersTypes($oAccount, $oFolderCollection, $aSystemFolders);
|
||||
}
|
||||
if ($oFolderCollection) {
|
||||
$aQuota = null;
|
||||
if ($this->GetCapa(Capa::QUOTA)) {
|
||||
try {
|
||||
// $aQuota = $this->ImapClient()->Quota();
|
||||
$aQuota = $this->ImapClient()->QuotaRoot();
|
||||
} catch (\Throwable $oException) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
if ($oFolderCollection)
|
||||
{
|
||||
$this->Plugins()->RunHook('filter.folders-complete', array($oAccount, $oFolderCollection));
|
||||
$aCapabilities = \array_values(\array_filter($this->ImapClient()->Capability(), function ($item) {
|
||||
return !\preg_match('/^(IMAP|AUTH|LOGIN|SASL)/', $item);
|
||||
}));
|
||||
|
||||
$aQuota = null;
|
||||
if ($this->GetCapa(Capa::QUOTA)) {
|
||||
try {
|
||||
// $aQuota = $this->MailClient()->Quota();
|
||||
$aQuota = $this->MailClient()->QuotaRoot();
|
||||
} catch (\Throwable $oException) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
$aCapabilities = \array_filter($this->MailClient()->Capability(), function($item){
|
||||
return !\preg_match('/^(IMAP|AUTH|LOGIN|SASL)/', $item);
|
||||
});
|
||||
if (!$this->Config()->Get('imap', 'use_list_status', true)) {
|
||||
$key = \array_search('LIST-STATUS', $aCapabilities);
|
||||
if (false !== $key) {
|
||||
unset($aCapabilities[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$oFolderCollection = \array_merge(
|
||||
$oFolderCollection->jsonSerialize(),
|
||||
array(
|
||||
'quotaUsage' => $aQuota ? $aQuota[0] * 1024 : null,
|
||||
'quotaLimit' => $aQuota ? $aQuota[1] * 1024 : null,
|
||||
'Namespace' => $sNamespace,
|
||||
'IsThreadsSupported' => $this->MailClient()->IsThreadsSupported(),
|
||||
'Optimized' => $oFolderCollection->Optimized,
|
||||
'CountRec' => $oFolderCollection->TotalCount,
|
||||
'SystemFolders' => empty($aSystemFolders) ? null : $aSystemFolders,
|
||||
'Capabilities' => \array_values($aCapabilities)
|
||||
)
|
||||
);
|
||||
}
|
||||
$oFolderCollection = \array_merge(
|
||||
$oFolderCollection->jsonSerialize(),
|
||||
array(
|
||||
'quotaUsage' => $aQuota ? $aQuota[0] * 1024 : null,
|
||||
'quotaLimit' => $aQuota ? $aQuota[1] * 1024 : null,
|
||||
'namespace' => $this->ImapClient()->GetPersonalNamespace(),
|
||||
'capabilities' => $aCapabilities
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $oFolderCollection);
|
||||
return $this->DefaultResponse($oFolderCollection);
|
||||
}
|
||||
|
||||
public function DoFolderCreate() : array
|
||||
|
|
@ -222,13 +93,13 @@ trait Folders
|
|||
try
|
||||
{
|
||||
$oFolder = $this->MailClient()->FolderCreate(
|
||||
$this->GetActionParam('Folder', ''),
|
||||
$this->GetActionParam('Parent', ''),
|
||||
!!$this->GetActionParam('Subscribe', 1)
|
||||
$this->GetActionParam('folder', ''),
|
||||
$this->GetActionParam('parent', ''),
|
||||
!empty($this->GetActionParam('subscribe', 0))
|
||||
);
|
||||
|
||||
// FolderInformation(string $sFolderName, int $iPrevUidNext = 0, array $aUids = array())
|
||||
return $this->DefaultResponse(__FUNCTION__, $oFolder);
|
||||
return $this->DefaultResponse($oFolder);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
|
|
@ -239,82 +110,60 @@ trait Folders
|
|||
public function DoFolderSetMetadata() : array
|
||||
{
|
||||
$this->initMailClientConnection();
|
||||
$sFolderFullName = $this->GetActionParam('Folder');
|
||||
$sMetadataKey = $this->GetActionParam('Key');
|
||||
$sFolderFullName = $this->GetActionParam('folder');
|
||||
$sMetadataKey = $this->GetActionParam('key');
|
||||
if ($sFolderFullName && $sMetadataKey) {
|
||||
$this->MailClient()->FolderSetMetadata($sFolderFullName, [
|
||||
$sMetadataKey => $this->GetActionParam('Value') ?: null
|
||||
$this->ImapClient()->FolderSetMetadata($sFolderFullName, [
|
||||
$sMetadataKey => $this->GetActionParam('value') ?: null
|
||||
]);
|
||||
}
|
||||
return $this->TrueResponse(__FUNCTION__);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
|
||||
public function DoFolderSubscribe() : array
|
||||
{
|
||||
$this->initMailClientConnection();
|
||||
|
||||
$sFolderFullName = $this->GetActionParam('Folder', '');
|
||||
$bSubscribe = '1' === (string) $this->GetActionParam('Subscribe', '0');
|
||||
$sFolderFullName = $this->GetActionParam('folder', '');
|
||||
$bSubscribe = !empty($this->GetActionParam('subscribe', 0));
|
||||
|
||||
try
|
||||
{
|
||||
$this->MailClient()->FolderSubscribe($sFolderFullName, $bSubscribe);
|
||||
$this->ImapClient()->{$bSubscribe ? 'FolderSubscribe' : 'FolderUnsubscribe'}($sFolderFullName);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
if ($bSubscribe)
|
||||
{
|
||||
throw new ClientException(Notifications::CantSubscribeFolder, $oException);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ClientException(Notifications::CantUnsubscribeFolder, $oException);
|
||||
}
|
||||
throw new ClientException(
|
||||
$bSubscribe ? Notifications::CantSubscribeFolder : Notifications::CantUnsubscribeFolder,
|
||||
$oException
|
||||
);
|
||||
}
|
||||
|
||||
return $this->TrueResponse(__FUNCTION__);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
|
||||
public function DoFolderCheckable() : array
|
||||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
$sFolderFullName = $this->GetActionParam('Folder', '');
|
||||
$bCheckable = '1' === (string) $this->GetActionParam('Checkable', '0');
|
||||
$sFolderFullName = $this->GetActionParam('folder', '');
|
||||
|
||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||
|
||||
$sCheckableFolder = $oSettingsLocal->GetConf('CheckableFolder', '[]');
|
||||
$aCheckableFolder = \json_decode($sCheckableFolder);
|
||||
|
||||
if (!\is_array($aCheckableFolder))
|
||||
{
|
||||
$aCheckableFolder = \json_decode($oSettingsLocal->GetConf('CheckableFolder', '[]'));
|
||||
if (!\is_array($aCheckableFolder)) {
|
||||
$aCheckableFolder = array();
|
||||
}
|
||||
|
||||
if ($bCheckable)
|
||||
{
|
||||
if (!empty($this->GetActionParam('checkable', '0'))) {
|
||||
$aCheckableFolder[] = $sFolderFullName;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aCheckableFolderNew = array();
|
||||
foreach ($aCheckableFolder as $sFolder)
|
||||
{
|
||||
if ($sFolder !== $sFolderFullName)
|
||||
{
|
||||
$aCheckableFolderNew[] = $sFolder;
|
||||
}
|
||||
}
|
||||
$aCheckableFolder = $aCheckableFolderNew;
|
||||
} else if (($key = \array_search($sFolderFullName, $aCheckableFolder)) !== false) {
|
||||
\array_splice($aCheckableFolder, $key, 1);
|
||||
}
|
||||
|
||||
$aCheckableFolder = \array_unique($aCheckableFolder);
|
||||
$oSettingsLocal->SetConf('CheckableFolder', \json_encode(\array_unique($aCheckableFolder)));
|
||||
|
||||
$oSettingsLocal->SetConf('CheckableFolder', \json_encode($aCheckableFolder));
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__,
|
||||
$this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal));
|
||||
return $this->DefaultResponse($this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -327,9 +176,9 @@ trait Folders
|
|||
try
|
||||
{
|
||||
$this->MailClient()->FolderMove(
|
||||
$this->GetActionParam('Folder', ''),
|
||||
$this->GetActionParam('NewFolder', ''),
|
||||
!!$this->GetActionParam('Subscribe', 1)
|
||||
$this->GetActionParam('folder', ''),
|
||||
$this->GetActionParam('newFolder', ''),
|
||||
!empty($this->GetActionParam('subscribe', 1))
|
||||
);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
|
|
@ -337,7 +186,7 @@ trait Folders
|
|||
throw new ClientException(Notifications::CantRenameFolder, $oException);
|
||||
}
|
||||
|
||||
return $this->TrueResponse(__FUNCTION__);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -347,13 +196,13 @@ trait Folders
|
|||
{
|
||||
$this->initMailClientConnection();
|
||||
|
||||
$sName = $this->GetActionParam('NewFolderName', '');
|
||||
$sName = $this->GetActionParam('newFolderName', '');
|
||||
try
|
||||
{
|
||||
$sFullName = $this->MailClient()->FolderRename(
|
||||
$this->GetActionParam('Folder', ''),
|
||||
$this->GetActionParam('folder', ''),
|
||||
$sName,
|
||||
!!$this->GetActionParam('Subscribe', 1)
|
||||
!empty($this->GetActionParam('subscribe', 1))
|
||||
);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
|
|
@ -362,9 +211,9 @@ trait Folders
|
|||
}
|
||||
|
||||
// FolderInformation(string $sFolderName, int $iPrevUidNext = 0, array $aUids = array())
|
||||
return $this->DefaultResponse(__FUNCTION__, array(
|
||||
'Name' => $sName,
|
||||
'FullName' => $sFullName,
|
||||
return $this->DefaultResponse(array(
|
||||
'name' => $sName,
|
||||
'fullName' => $sFullName,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -377,7 +226,7 @@ trait Folders
|
|||
|
||||
try
|
||||
{
|
||||
$this->MailClient()->FolderDelete($this->GetActionParam('Folder', ''));
|
||||
$this->ImapClient()->FolderDelete($this->GetActionParam('folder', ''));
|
||||
}
|
||||
catch (\MailSo\Mail\Exceptions\NonEmptyFolder $oException)
|
||||
{
|
||||
|
|
@ -388,7 +237,7 @@ trait Folders
|
|||
throw new ClientException(Notifications::CantDeleteFolder, $oException);
|
||||
}
|
||||
|
||||
return $this->TrueResponse(__FUNCTION__);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -400,14 +249,14 @@ trait Folders
|
|||
|
||||
try
|
||||
{
|
||||
$this->MailClient()->FolderClear($this->GetActionParam('Folder', ''));
|
||||
$this->ImapClient()->FolderClear($this->GetActionParam('folder', ''));
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
throw new ClientException(Notifications::MailServerError, $oException);
|
||||
}
|
||||
|
||||
return $this->TrueResponse(__FUNCTION__);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -419,12 +268,11 @@ trait Folders
|
|||
|
||||
try
|
||||
{
|
||||
$aInboxInformation = $this->MailClient()->FolderInformation(
|
||||
$this->GetActionParam('Folder', ''),
|
||||
(int) $this->GetActionParam('UidNext', 0),
|
||||
new \MailSo\Imap\SequenceSet($this->GetActionParam('FlagsUids', []))
|
||||
);
|
||||
return $this->DefaultResponse(__FUNCTION__, $aInboxInformation);
|
||||
return $this->DefaultResponse($this->MailClient()->FolderInformation(
|
||||
$this->GetActionParam('folder', ''),
|
||||
(int) $this->GetActionParam('uidNext', 0),
|
||||
new \MailSo\Imap\SequenceSet($this->GetActionParam('flagsUids', []))
|
||||
));
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
|
|
@ -439,38 +287,24 @@ trait Folders
|
|||
{
|
||||
$aResult = array();
|
||||
|
||||
$aFolders = $this->GetActionParam('Folders', null);
|
||||
if (\is_array($aFolders))
|
||||
{
|
||||
$aFolders = $this->GetActionParam('folders', null);
|
||||
if (\is_array($aFolders)) {
|
||||
$this->initMailClientConnection();
|
||||
|
||||
$aFolders = \array_unique($aFolders);
|
||||
foreach ($aFolders as $sFolder)
|
||||
{
|
||||
if (\strlen($sFolder) && 'INBOX' !== \strtoupper($sFolder))
|
||||
foreach ($aFolders as $sFolder) {
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
$aInboxInformation = $this->MailClient()->FolderInformation($sFolder);
|
||||
if (isset($aInboxInformation['Folder']))
|
||||
{
|
||||
$aResult[] = [
|
||||
'Folder' => $aInboxInformation['Folder'],
|
||||
'Hash' => $aInboxInformation['Hash'],
|
||||
'totalEmails' => $aInboxInformation['totalEmails'],
|
||||
'unreadEmails' => $aInboxInformation['unreadEmails'],
|
||||
];
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$this->Logger()->WriteException($oException);
|
||||
}
|
||||
$aResult[] = $this->MailClient()->FolderInformation($sFolder);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$this->logException($oException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $aResult);
|
||||
return $this->DefaultResponse($aResult);
|
||||
}
|
||||
|
||||
public function DoSystemFoldersUpdate() : array
|
||||
|
|
@ -479,132 +313,12 @@ trait Folders
|
|||
|
||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||
|
||||
$oSettingsLocal->SetConf('SentFolder', $this->GetActionParam('Sent', ''));
|
||||
$oSettingsLocal->SetConf('DraftFolder', $this->GetActionParam('Drafts', ''));
|
||||
$oSettingsLocal->SetConf('SpamFolder', $this->GetActionParam('Spam', ''));
|
||||
$oSettingsLocal->SetConf('TrashFolder', $this->GetActionParam('Trash', ''));
|
||||
$oSettingsLocal->SetConf('ArchiveFolder', $this->GetActionParam('Archive', ''));
|
||||
$oSettingsLocal->SetConf('SentFolder', $this->GetActionParam('sent', ''));
|
||||
$oSettingsLocal->SetConf('DraftsFolder', $this->GetActionParam('drafts', ''));
|
||||
$oSettingsLocal->SetConf('JunkFolder', $this->GetActionParam('junk', ''));
|
||||
$oSettingsLocal->SetConf('TrashFolder', $this->GetActionParam('trash', ''));
|
||||
$oSettingsLocal->SetConf('ArchiveFolder', $this->GetActionParam('archive', ''));
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__,
|
||||
$this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal));
|
||||
}
|
||||
|
||||
private function recFoldersTypes(\RainLoop\Model\Account $oAccount, \MailSo\Mail\FolderCollection $oFolders, array &$aResult, bool $bListFolderTypes = true) : void
|
||||
{
|
||||
if ($oFolders->Count())
|
||||
{
|
||||
if ($bListFolderTypes)
|
||||
{
|
||||
foreach ($oFolders as $oFolder)
|
||||
{
|
||||
$iFolderType = $oFolder->GetFolderListType();
|
||||
if (!isset($aResult[$iFolderType]) && \in_array($iFolderType, array(
|
||||
FolderType::INBOX,
|
||||
FolderType::SENT,
|
||||
FolderType::DRAFTS,
|
||||
FolderType::JUNK,
|
||||
FolderType::TRASH,
|
||||
FolderType::ARCHIVE
|
||||
)))
|
||||
{
|
||||
$aResult[$iFolderType] = $oFolder->FullName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aMap = $this->systemFoldersNames($oAccount);
|
||||
foreach ($oFolders as $oFolder)
|
||||
{
|
||||
$sName = $oFolder->Name();
|
||||
$sFullName = $oFolder->FullName();
|
||||
|
||||
if (isset($aMap[$sName]) || isset($aMap[$sFullName]))
|
||||
{
|
||||
$iFolderType = isset($aMap[$sName]) ? $aMap[$sName] : $aMap[$sFullName];
|
||||
if ((!isset($aResult[$iFolderType]) || $sName === $sFullName || "INBOX{$oFolder->Delimiter()}{$sName}" === $sFullName) && \in_array($iFolderType, array(
|
||||
FolderType::INBOX,
|
||||
FolderType::SENT,
|
||||
FolderType::DRAFTS,
|
||||
FolderType::JUNK,
|
||||
FolderType::TRASH,
|
||||
FolderType::ARCHIVE
|
||||
)))
|
||||
{
|
||||
$aResult[$iFolderType] = $oFolder->FullName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @staticvar array $aCache
|
||||
*/
|
||||
private function systemFoldersNames(\RainLoop\Model\Account $oAccount) : array
|
||||
{
|
||||
static $aCache = null;
|
||||
if (null === $aCache)
|
||||
{
|
||||
$aCache = array(
|
||||
|
||||
'Sent' => FolderType::SENT,
|
||||
'Send' => FolderType::SENT,
|
||||
|
||||
'Outbox' => FolderType::SENT,
|
||||
'Out box' => FolderType::SENT,
|
||||
|
||||
'Sent Item' => FolderType::SENT,
|
||||
'Sent Items' => FolderType::SENT,
|
||||
'Send Item' => FolderType::SENT,
|
||||
'Send Items' => FolderType::SENT,
|
||||
'Sent Mail' => FolderType::SENT,
|
||||
'Sent Mails' => FolderType::SENT,
|
||||
'Send Mail' => FolderType::SENT,
|
||||
'Send Mails' => FolderType::SENT,
|
||||
|
||||
'Drafts' => FolderType::DRAFTS,
|
||||
|
||||
'Draft' => FolderType::DRAFTS,
|
||||
'Draft Mail' => FolderType::DRAFTS,
|
||||
'Draft Mails' => FolderType::DRAFTS,
|
||||
'Drafts Mail' => FolderType::DRAFTS,
|
||||
'Drafts Mails' => FolderType::DRAFTS,
|
||||
|
||||
'Junk E-mail' => FolderType::JUNK,
|
||||
|
||||
'Spam' => FolderType::JUNK,
|
||||
'Spams' => FolderType::JUNK,
|
||||
|
||||
'Junk' => FolderType::JUNK,
|
||||
'Bulk Mail' => FolderType::JUNK,
|
||||
'Bulk Mails' => FolderType::JUNK,
|
||||
|
||||
'Deleted Items' => FolderType::TRASH,
|
||||
|
||||
'Trash' => FolderType::TRASH,
|
||||
'Deleted' => FolderType::TRASH,
|
||||
'Bin' => FolderType::TRASH,
|
||||
|
||||
'Archive' => FolderType::ARCHIVE,
|
||||
'Archives' => FolderType::ARCHIVE,
|
||||
|
||||
'All' => FolderType::ALL,
|
||||
'All Mail' => FolderType::ALL,
|
||||
'All Mails' => FolderType::ALL,
|
||||
);
|
||||
|
||||
$aNewCache = array();
|
||||
foreach ($aCache as $sKey => $iType)
|
||||
{
|
||||
$aNewCache[$sKey] = $iType;
|
||||
$aNewCache[\str_replace(' ', '', $sKey)] = $iType;
|
||||
}
|
||||
|
||||
$aCache = $aNewCache;
|
||||
|
||||
$this->Plugins()->RunHook('filter.system-folders-names', array($oAccount, &$aCache));
|
||||
}
|
||||
|
||||
return $aCache;
|
||||
return $this->DefaultResponse($this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ trait Localization
|
|||
if ($oAccount = $this->getAccountFromToken(false)) {
|
||||
if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)
|
||||
&& ($oSettings = $this->SettingsProvider()->Load($oAccount))) {
|
||||
$sLanguage = $oSettings->GetConf('Language', $sLanguage);
|
||||
$sLanguage = $oSettings->GetConf('language', $sLanguage);
|
||||
}
|
||||
} else if ($oConfig->Get('login', 'allow_languages_on_login', true) && $oConfig->Get('login', 'determine_user_language', true)) {
|
||||
$sLanguage = $this->ValidateLanguage($this->detectUserLanguage($bAdmin), $sLanguage, false);
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace RainLoop\Actions;
|
||||
|
||||
use SnappyMail\PGP\Backup;
|
||||
|
||||
trait Pgp
|
||||
{
|
||||
/**
|
||||
|
|
@ -74,15 +76,15 @@ trait Pgp
|
|||
{
|
||||
$GPG = $this->GnuPG();
|
||||
if (!$GPG) {
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
$GPG->addDecryptKey(
|
||||
$this->GetActionParam('KeyId', ''),
|
||||
$this->GetActionParam('Passphrase', '')
|
||||
$this->GetActionParam('keyId', ''),
|
||||
$this->GetActionParam('passphrase', '')
|
||||
);
|
||||
|
||||
$sData = $this->GetActionParam('Data', '');
|
||||
$sData = $this->GetActionParam('data', '');
|
||||
$oPart = null;
|
||||
$result = [
|
||||
'data' => '',
|
||||
|
|
@ -102,9 +104,9 @@ trait Pgp
|
|||
// $oPart = \MailSo\Mime\Part::FromStream($rStreamHandle);
|
||||
}
|
||||
},
|
||||
$this->GetActionParam('Folder', ''),
|
||||
(int) $this->GetActionParam('Uid', ''),
|
||||
$this->GetActionParam('PartId', '')
|
||||
$this->GetActionParam('folder', ''),
|
||||
(int) $this->GetActionParam('uid', ''),
|
||||
$this->GetActionParam('partId', '')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -113,21 +115,21 @@ trait Pgp
|
|||
// $result['signatures'] = $oPart->SubParts[0];
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $result);
|
||||
return $this->DefaultResponse($result);
|
||||
}
|
||||
|
||||
public function DoGnupgGetKeys() : array
|
||||
{
|
||||
$GPG = $this->GnuPG();
|
||||
return $this->DefaultResponse(__FUNCTION__, $GPG ? $GPG->keyInfo('') : false);
|
||||
return $this->DefaultResponse($GPG ? $GPG->keyInfo('') : false);
|
||||
}
|
||||
|
||||
public function DoGnupgExportKey() : array
|
||||
{
|
||||
$GPG = $this->GnuPG();
|
||||
return $this->DefaultResponse(__FUNCTION__, $GPG ? $GPG->export(
|
||||
$this->GetActionParam('KeyId', ''),
|
||||
$this->GetActionParam('Passphrase', '')
|
||||
return $this->DefaultResponse($GPG ? $GPG->export(
|
||||
$this->GetActionParam('keyId', ''),
|
||||
$this->GetActionParam('passphrase', '')
|
||||
) : false);
|
||||
}
|
||||
|
||||
|
|
@ -136,29 +138,29 @@ trait Pgp
|
|||
$fingerprint = false;
|
||||
$GPG = $this->GnuPG();
|
||||
if ($GPG) {
|
||||
$sName = $this->GetActionParam('Name', '');
|
||||
$sEmail = $this->GetActionParam('Email', '');
|
||||
$sName = $this->GetActionParam('name', '');
|
||||
$sEmail = $this->GetActionParam('email', '');
|
||||
$fingerprint = $GPG->generateKey(
|
||||
$sName ? "{$sName} <{$sEmail}>" : $sEmail,
|
||||
$this->GetActionParam('Passphrase', '')
|
||||
$this->GetActionParam('passphrase', '')
|
||||
);
|
||||
}
|
||||
return $this->DefaultResponse(__FUNCTION__, $fingerprint);
|
||||
return $this->DefaultResponse($fingerprint);
|
||||
}
|
||||
|
||||
public function DoGnupgDeleteKey() : array
|
||||
{
|
||||
$GPG = $this->GnuPG();
|
||||
$sKeyId = $this->GetActionParam('KeyId', '');
|
||||
$sKeyId = $this->GetActionParam('keyId', '');
|
||||
$bPrivate = !!$this->GetActionParam('isPrivate', 0);
|
||||
return $this->DefaultResponse(__FUNCTION__, $GPG ? $GPG->deleteKey($sKeyId, $bPrivate) : false);
|
||||
return $this->DefaultResponse($GPG ? $GPG->deleteKey($sKeyId, $bPrivate) : false);
|
||||
}
|
||||
|
||||
public function DoGnupgImportKey() : array
|
||||
{
|
||||
$sKey = $this->GetActionParam('Key', '');
|
||||
$sKeyId = $this->GetActionParam('KeyId', '');
|
||||
$sEmail = $this->GetActionParam('Email', '');
|
||||
$sKey = $this->GetActionParam('key', '');
|
||||
$sKeyId = $this->GetActionParam('keyId', '');
|
||||
$sEmail = $this->GetActionParam('email', '');
|
||||
|
||||
if (!$sKey) {
|
||||
try {
|
||||
|
|
@ -182,7 +184,7 @@ trait Pgp
|
|||
}
|
||||
|
||||
$GPG = $sKey ? $this->GnuPG() : null;
|
||||
return $this->DefaultResponse(__FUNCTION__, $GPG ? $GPG->import($sKey) : false);
|
||||
return $this->DefaultResponse($GPG ? $GPG->import($sKey) : false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -191,35 +193,7 @@ trait Pgp
|
|||
*/
|
||||
public function DoGetStoredPGPKeys() : array
|
||||
{
|
||||
$oAccount = $this->getMainAccountFromToken();
|
||||
if (!$oAccount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dir = $this->StorageProvider()->GenerateFilePath(
|
||||
$oAccount,
|
||||
\RainLoop\Providers\Storage\Enumerations\StorageType::PGP
|
||||
);
|
||||
|
||||
$keys = [];
|
||||
foreach (\glob("{$dir}/*") as $file) {
|
||||
if (\is_file($file)) {
|
||||
if ('.asc' === \substr($file, -4)) {
|
||||
$keys[] = \file_get_contents($file);
|
||||
} else if ('.key' === \substr($file, -4)) {
|
||||
$key = \json_decode(\file_get_contents($file), true);
|
||||
$mac = \array_pop($key);
|
||||
$hash = $oAccount->CryptKey();
|
||||
if ($mac === \hash_hmac('sha1', $key[2], $hash)) {
|
||||
$key[1] = \base64_decode($key[1]);
|
||||
$key[2] = \base64_decode($key[2]);
|
||||
$keys[] = \SnappyMail\Crypt::Decrypt($key, $hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $keys);
|
||||
return $this->DefaultResponse(\SnappyMail\PGP\Backup::getKeys());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -232,16 +206,16 @@ trait Pgp
|
|||
$privateKey = $this->GetActionParam('privateKey', '');
|
||||
|
||||
$result = [
|
||||
'onServer' => [false, false, false],
|
||||
'inGnuPG' => [false, false, false]
|
||||
'onServer' => [false, false],
|
||||
'inGnuPG' => [false, false]
|
||||
];
|
||||
|
||||
$onServer = (int) $this->GetActionParam('onServer', 0);
|
||||
if ($publicKey && $onServer & 1) {
|
||||
$result['onServer'][0] = $this->StorePGPKey($publicKey);
|
||||
$result['onServer'][0] = Backup::PGPKey($publicKey);
|
||||
}
|
||||
if ($privateKey && $onServer & 2) {
|
||||
$result['onServer'][1] = $this->StorePGPKey($privateKey);
|
||||
$result['onServer'][1] = Backup::PGPKey($privateKey);
|
||||
}
|
||||
|
||||
$inGnuPG = (int) $this->GetActionParam('inGnuPG', 0);
|
||||
|
|
@ -256,7 +230,7 @@ trait Pgp
|
|||
}
|
||||
|
||||
// $revocationCertificate = $this->GetActionParam('revocationCertificate', '');
|
||||
return $this->DefaultResponse(__FUNCTION__, $result);
|
||||
return $this->DefaultResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -265,34 +239,8 @@ trait Pgp
|
|||
*/
|
||||
public function DoStorePGPKey() : array
|
||||
{
|
||||
$key = $this->GetActionParam('Key', '');
|
||||
$keyId = $this->GetActionParam('KeyId', '');
|
||||
return $this->DefaultResponse(__FUNCTION__, ($key && $keyId && $this->StorePGPKey($key, $keyId)));
|
||||
$key = $this->GetActionParam('key', '');
|
||||
$keyId = $this->GetActionParam('keyId', '');
|
||||
return $this->DefaultResponse(($key && $keyId && Backup::PGPKey($key, $keyId)));
|
||||
}
|
||||
|
||||
private function StorePGPKey(string $key, string $keyId = '') : bool
|
||||
{
|
||||
$oAccount = $this->getMainAccountFromToken();
|
||||
if ($oAccount) {
|
||||
$keyId = $keyId ? "0x{$keyId}" : \sha1($key);
|
||||
$dir = $this->StorageProvider()->GenerateFilePath(
|
||||
$oAccount,
|
||||
\RainLoop\Providers\Storage\Enumerations\StorageType::PGP,
|
||||
true
|
||||
);
|
||||
if (\str_contains($key, 'PGP PRIVATE KEY')) {
|
||||
$hash = $oAccount->CryptKey();
|
||||
$key = \SnappyMail\Crypt::Encrypt($key, $hash);
|
||||
$key[1] = \base64_encode($key[1]);
|
||||
$key[2] = \base64_encode($key[2]);
|
||||
$key[] = \hash_hmac('sha1', $key[2], $hash);
|
||||
return !!\file_put_contents("{$dir}{$keyId}.key", \json_encode($key));
|
||||
}
|
||||
if (\str_contains($key, 'PGP PUBLIC KEY')) {
|
||||
return !!\file_put_contents("{$dir}{$keyId}_public.asc", $key);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,22 +9,28 @@ trait Raw
|
|||
*/
|
||||
public function RawViewAsPlain() : bool
|
||||
{
|
||||
$this->initMailClientConnection();
|
||||
|
||||
$sRawKey = (string) $this->GetActionParam('RawKey', '');
|
||||
$aValues = $this->getDecodedRawKeyValue($sRawKey);
|
||||
|
||||
$sFolder = isset($aValues['Folder']) ? (string) $aValues['Folder'] : '';
|
||||
$iUid = isset($aValues['Uid']) ? (int) $aValues['Uid'] : 0;
|
||||
$sMimeIndex = isset($aValues['MimeIndex']) ? (string) $aValues['MimeIndex'] : '';
|
||||
|
||||
\header('Content-Type: text/plain');
|
||||
|
||||
return $this->MailClient()->MessageMimeStream(function ($rResource) {
|
||||
if (\is_resource($rResource)) {
|
||||
\MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource);
|
||||
}
|
||||
}, $sFolder, $iUid, $sMimeIndex);
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
$sRawKey = $this->GetActionParam('RawKey', '');
|
||||
$aValues = $this->decodeRawKey($sRawKey);
|
||||
if (!empty($aValues['folder']) && !empty($aValues['uid'])
|
||||
&& !empty($aValues['accountHash']) && $aValues['accountHash'] === $oAccount->Hash()
|
||||
) {
|
||||
$this->verifyCacheByKey($sRawKey);
|
||||
$this->initMailClientConnection();
|
||||
\header('Content-Type: text/plain');
|
||||
return $this->MailClient()->MessageMimeStream(
|
||||
function ($rResource) use ($sRawKey) {
|
||||
if (\is_resource($rResource)) {
|
||||
$this->cacheByKey($sRawKey);
|
||||
\MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource);
|
||||
}
|
||||
},
|
||||
(string) $aValues['folder'],
|
||||
(int) $aValues['uid'],
|
||||
isset($aValues['mimeIndex']) ? (string) $aValues['mimeIndex'] : ''
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function RawDownload() : bool
|
||||
|
|
@ -49,60 +55,19 @@ trait Raw
|
|||
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
$sData = $this->StorageProvider()->Get($oAccount,
|
||||
$mData = $this->StorageProvider()->Get($oAccount,
|
||||
\RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
|
||||
'background'
|
||||
);
|
||||
|
||||
if (!empty($sData))
|
||||
{
|
||||
$aData = \json_decode($sData, true);
|
||||
unset($sData);
|
||||
|
||||
if (!empty($aData['ContentType']) && !empty($aData['Raw']) &&
|
||||
\in_array($aData['ContentType'], array('image/png', 'image/jpg', 'image/jpeg')))
|
||||
{
|
||||
if (!empty($mData)) {
|
||||
$mData = \json_decode($mData, true);
|
||||
if (!empty($mData['ContentType']) && !empty($mData['Raw'])) {
|
||||
$this->cacheByKey($sRawKey);
|
||||
|
||||
\header('Content-Type: '.$aData['ContentType']);
|
||||
echo \base64_decode($aData['Raw']);
|
||||
unset($aData);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function RawPublic() : bool
|
||||
{
|
||||
$sRawKey = (string) $this->GetActionParam('RawKey', '');
|
||||
$this->verifyCacheByKey($sRawKey);
|
||||
|
||||
$sHash = $sRawKey;
|
||||
$sData = '';
|
||||
|
||||
if (!empty($sHash))
|
||||
{
|
||||
$sData = $this->StorageProvider()->Get(null,
|
||||
\RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY,
|
||||
\RainLoop\KeyPathHelper::PublicFile($sHash)
|
||||
);
|
||||
}
|
||||
|
||||
$aMatch = array();
|
||||
if (!empty($sData) && 0 === \strpos($sData, 'data:') &&
|
||||
\preg_match('/^data:([^:]+):/', $sData, $aMatch) && !empty($aMatch[1]))
|
||||
{
|
||||
$sContentType = \trim($aMatch[1]);
|
||||
if (\in_array($sContentType, array('image/png', 'image/jpg', 'image/jpeg')))
|
||||
{
|
||||
$this->cacheByKey($sRawKey);
|
||||
|
||||
\header('Content-Type: '.$sContentType);
|
||||
echo \preg_replace('/^data:[^:]+:/', '', $sData);
|
||||
unset($sData);
|
||||
\header('Content-Type: '.$mData['ContentType']);
|
||||
echo \base64_decode($mData['Raw']);
|
||||
unset($mData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -111,52 +76,51 @@ trait Raw
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message, Message Attachment or Zip
|
||||
*/
|
||||
private function rawSmart(bool $bDownload, bool $bThumbnail = false) : bool
|
||||
{
|
||||
$sRawKey = (string) $this->GetActionParam('RawKey', '');
|
||||
|
||||
$aValues = $this->getDecodedRawKeyValue($sRawKey);
|
||||
|
||||
$sRange = $this->Http()->GetHeader('Range');
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
$aValues = $this->decodeRawKey($sRawKey);
|
||||
if (empty($aValues['accountHash']) || $aValues['accountHash'] !== $oAccount->Hash()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sRange = \MailSo\Base\Http::GetHeader('Range');
|
||||
$aMatch = array();
|
||||
$sRangeStart = $sRangeEnd = '';
|
||||
$bIsRangeRequest = false;
|
||||
|
||||
if (!empty($sRange) && 'bytes=0-' !== \strtolower($sRange) && \preg_match('/^bytes=([0-9]+)-([0-9]*)/i', \trim($sRange), $aMatch))
|
||||
if (!empty($sRange) && 'bytes=0-' !== \strtolower($sRange)
|
||||
&& \preg_match('/^bytes=([0-9]+)-([0-9]*)/i', \trim($sRange), $aMatch))
|
||||
{
|
||||
$sRangeStart = $aMatch[1];
|
||||
$sRangeEnd = $aMatch[2];
|
||||
|
||||
$bIsRangeRequest = true;
|
||||
}
|
||||
|
||||
$sFolder = isset($aValues['Folder']) ? (string) $aValues['Folder'] : '';
|
||||
$iUid = isset($aValues['Uid']) ? (int) $aValues['Uid'] : 0;
|
||||
$sMimeIndex = isset($aValues['MimeIndex']) ? (string) $aValues['MimeIndex'] : '';
|
||||
$sMimeIndex = isset($aValues['mimeIndex']) ? (string) $aValues['mimeIndex'] : '';
|
||||
$sContentTypeIn = isset($aValues['mimeType']) ? (string) $aValues['mimeType'] : '';
|
||||
$sFileNameIn = isset($aValues['fileName']) ? (string) $aValues['fileName'] : '';
|
||||
|
||||
$sContentTypeIn = isset($aValues['MimeType']) ? (string) $aValues['MimeType'] : '';
|
||||
$sFileNameIn = isset($aValues['FileName']) ? (string) $aValues['FileName'] : '';
|
||||
$sFileHashIn = isset($aValues['FileHash']) ? (string) $aValues['FileHash'] : '';
|
||||
|
||||
if (!empty($sFileHashIn))
|
||||
{
|
||||
if (!empty($aValues['fileHash'])) {
|
||||
$this->verifyCacheByKey($sRawKey);
|
||||
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
// https://github.com/the-djmaze/snappymail/issues/144
|
||||
if ('.pdf' === \substr($sFileNameIn,-4)) {
|
||||
$sContentTypeOut = 'application/pdf'; // application/octet-stream
|
||||
} else {
|
||||
$sContentTypeOut = $sContentTypeIn ?: \MailSo\Base\Utils::MimeContentType($sFileNameIn);
|
||||
$sContentTypeOut = $sContentTypeIn
|
||||
?: \SnappyMail\File\MimeType::fromFilename($sFileNameIn)
|
||||
?: 'application/octet-stream';
|
||||
}
|
||||
|
||||
$sFileNameOut = $this->MainClearFileName($sFileNameIn, $sContentTypeIn, $sMimeIndex);
|
||||
|
||||
$rResource = $this->FilesProvider()->GetFile($oAccount, $sFileHashIn);
|
||||
if (\is_resource($rResource))
|
||||
{
|
||||
$rResource = $this->FilesProvider()->GetFile($oAccount, (string) $aValues['fileHash']);
|
||||
if (\is_resource($rResource)) {
|
||||
\header('Content-Type: '.$sContentTypeOut);
|
||||
\header('Content-Disposition: attachment; '.
|
||||
\trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)));
|
||||
|
|
@ -170,25 +134,35 @@ trait Raw
|
|||
|
||||
return false;
|
||||
}
|
||||
else if (!empty($sFolder) && 0 < $iUid)
|
||||
{
|
||||
$this->verifyCacheByKey($sRawKey);
|
||||
|
||||
$sFolder = isset($aValues['folder']) ? (string) $aValues['folder'] : '';
|
||||
$iUid = isset($aValues['uid']) ? (int) $aValues['uid'] : 0;
|
||||
if (empty($sFolder) || 1 > $iUid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$oAccount = $this->initMailClientConnection();
|
||||
$this->verifyCacheByKey($sRawKey);
|
||||
|
||||
$this->initMailClientConnection();
|
||||
|
||||
$self = $this;
|
||||
return $this->MailClient()->MessageMimeStream(
|
||||
function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use (
|
||||
$self, $oAccount, $sRawKey, $sContentTypeIn, $sFileNameIn, $bDownload, $bThumbnail,
|
||||
$self, $sRawKey, $sContentTypeIn, $sFileNameIn, $bDownload, $bThumbnail,
|
||||
$bIsRangeRequest, $sRangeStart, $sRangeEnd
|
||||
) {
|
||||
if ($oAccount && \is_resource($rResource))
|
||||
{
|
||||
if (\is_resource($rResource)) {
|
||||
\MailSo\Base\Utils::ResetTimeLimit();
|
||||
|
||||
$self->cacheByKey($sRawKey);
|
||||
|
||||
$self->logWrite(\print_r([
|
||||
$sFileName,
|
||||
$sContentType,
|
||||
$sFileNameIn,
|
||||
$sContentTypeIn
|
||||
],true), \LOG_DEBUG, 'RAW');
|
||||
|
||||
if ($sFileNameIn) {
|
||||
$sFileName = $sFileNameIn;
|
||||
}
|
||||
|
|
@ -197,30 +171,28 @@ trait Raw
|
|||
if ('.pdf' === \substr($sFileName, -4)) {
|
||||
// https://github.com/the-djmaze/snappymail/issues/144
|
||||
$sContentType = 'application/pdf';
|
||||
} else if ($sContentTypeIn) {
|
||||
$sContentType = $sContentTypeIn;
|
||||
} else if (!$sContentType) {
|
||||
$sContentType = $sFileName ? \MailSo\Base\Utils::MimeContentType($sFileName) : 'text/plain';
|
||||
} else {
|
||||
$sContentType = $sContentTypeIn
|
||||
?: $sContentType
|
||||
// ?: \SnappyMail\File\MimeType::fromStream($rResource, $sFileName)
|
||||
?: \SnappyMail\File\MimeType::fromFilename($sFileName)
|
||||
?: 'application/octet-stream';
|
||||
}
|
||||
|
||||
if (!$bDownload)
|
||||
{
|
||||
if (!$bDownload) {
|
||||
$bDetectImageOrientation = $self->Config()->Get('labs', 'image_exif_auto_rotate', false)
|
||||
// Mostly only JPEG has EXIF metadata
|
||||
&& 'image/jpeg' == $sContentType;
|
||||
try
|
||||
{
|
||||
if ($bThumbnail)
|
||||
{
|
||||
if ($bThumbnail) {
|
||||
$oImage = static::loadImage($rResource, $bDetectImageOrientation, 48);
|
||||
\header('Content-Disposition: inline; '.
|
||||
\trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileName.'_thumb60x60.png')));
|
||||
$oImage->show('png');
|
||||
// $oImage->show('webp'); // Little Britain: "Safari says NO"
|
||||
exit;
|
||||
}
|
||||
else if ($bDetectImageOrientation)
|
||||
{
|
||||
} else if ($bDetectImageOrientation) {
|
||||
$oImage = static::loadImage($rResource, $bDetectImageOrientation);
|
||||
\header('Content-Disposition: inline; '.
|
||||
\trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileName)));
|
||||
|
|
@ -259,24 +231,20 @@ trait Raw
|
|||
|
||||
\MailSo\Base\Http::StatusHeader(206);
|
||||
|
||||
$iRangeStart = (int) $sRangeStart;
|
||||
$iRangeEnd = (int) $sRangeEnd;
|
||||
$iRangeStart = \max(0, \intval($sRangeStart));
|
||||
$iRangeEnd = \max(0, \intval($sRangeEnd));
|
||||
|
||||
if ('' === $sRangeEnd) {
|
||||
$sLoadedData = 0 < $iRangeStart ? \substr($sLoadedData, $iRangeStart) : $sLoadedData;
|
||||
} else {
|
||||
if ($iRangeStart < $iRangeEnd) {
|
||||
$sLoadedData = \substr($sLoadedData, $iRangeStart, $iRangeEnd - $iRangeStart);
|
||||
} else {
|
||||
$sLoadedData = 0 < $iRangeStart ? \substr($sLoadedData, $iRangeStart) : $sLoadedData;
|
||||
}
|
||||
if ($iRangeEnd && $iRangeStart < $iRangeEnd) {
|
||||
$sLoadedData = \substr($sLoadedData, $iRangeStart, $iRangeEnd - $iRangeStart);
|
||||
} else if ($iRangeStart) {
|
||||
$sLoadedData = \substr($sLoadedData, $iRangeStart);
|
||||
}
|
||||
|
||||
$iContentLength = \strlen($sLoadedData);
|
||||
|
||||
if (0 < $iContentLength) {
|
||||
\header('Content-Length: '.$iContentLength);
|
||||
\header('Content-Range: bytes '.$sRangeStart.'-'.(0 < $iRangeEnd ? $iRangeEnd : $iFullContentLength - 1).'/'.$iFullContentLength);
|
||||
\header('Content-Range: bytes '.$sRangeStart.'-'.($iRangeEnd ?: $iFullContentLength - 1).'/'.$iFullContentLength);
|
||||
}
|
||||
} else {
|
||||
\header('Content-Length: '.\strlen($sLoadedData));
|
||||
|
|
|
|||
|
|
@ -9,128 +9,64 @@ use RainLoop\Utils;
|
|||
trait Response
|
||||
{
|
||||
/**
|
||||
* @param mixed $mResult = false
|
||||
* @param mixed $mResult
|
||||
*/
|
||||
public function DefaultResponse(string $sActionName, $mResult = false, array $aAdditionalParams = array()) : array
|
||||
public function DefaultResponse($mResult, array $aAdditionalParams = array(), string $sActionName = '') : array
|
||||
{
|
||||
$this->Plugins()->RunHook('main.default-response-data', array($sActionName, &$mResult));
|
||||
$aResponseItem = $this->mainDefaultResponse($sActionName, $mResult, $aAdditionalParams);
|
||||
$this->Plugins()->RunHook('main.default-response', array($sActionName, &$aResponseItem));
|
||||
return $aResponseItem;
|
||||
}
|
||||
|
||||
public function TrueResponse(string $sActionName, array $aAdditionalParams = array()) : array
|
||||
{
|
||||
$mResult = true;
|
||||
$this->Plugins()->RunHook('main.default-response-data', array($sActionName, &$mResult));
|
||||
$aResponseItem = $this->mainDefaultResponse($sActionName, $mResult, $aAdditionalParams);
|
||||
$this->Plugins()->RunHook('main.default-response', array($sActionName, &$aResponseItem));
|
||||
return $aResponseItem;
|
||||
}
|
||||
|
||||
public function FalseResponse(string $sActionName, ?int $iErrorCode = null, ?string $sErrorMessage = null, ?string $sAdditionalErrorMessage = null) : array
|
||||
{
|
||||
$mResult = false;
|
||||
$this->Plugins()
|
||||
->RunHook('main.default-response-data', array($sActionName, &$mResult))
|
||||
->RunHook('main.default-response-error-data', array($sActionName, &$iErrorCode, &$sErrorMessage))
|
||||
;
|
||||
|
||||
$aAdditionalParams = array();
|
||||
if (null !== $iErrorCode)
|
||||
{
|
||||
$aAdditionalParams['ErrorCode'] = (int) $iErrorCode;
|
||||
$aAdditionalParams['ErrorMessage'] = null === $sErrorMessage ? '' : (string) $sErrorMessage;
|
||||
$aAdditionalParams['ErrorMessageAdditional'] = null === $sAdditionalErrorMessage ? '' : (string) $sAdditionalErrorMessage;
|
||||
if (false === $mResult) {
|
||||
if (!isset($aAdditionalParams['ErrorCode'])) {
|
||||
$aAdditionalParams['ErrorCode'] = 0;
|
||||
}
|
||||
if (!isset($aAdditionalParams['ErrorMessage'])) {
|
||||
$aAdditionalParams['ErrorMessage'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$aResponseItem = $this->mainDefaultResponse($sActionName, $mResult, $aAdditionalParams);
|
||||
|
||||
$this->Plugins()->RunHook('main.default-response', array($sActionName, &$aResponseItem));
|
||||
return $aResponseItem;
|
||||
return \array_merge(array(
|
||||
// 'version' => APP_VERSION,
|
||||
'Action' => $sActionName,
|
||||
'Result' => $this->responseObject($mResult)
|
||||
), $aAdditionalParams);
|
||||
}
|
||||
|
||||
public function ExceptionResponse(string $sActionName, \Throwable $oException) : array
|
||||
public function TrueResponse(array $aAdditionalParams = array()) : array
|
||||
{
|
||||
$iErrorCode = null;
|
||||
$sErrorMessage = null;
|
||||
$sErrorMessageAdditional = null;
|
||||
return $this->DefaultResponse(true, $aAdditionalParams);
|
||||
}
|
||||
|
||||
if ($oException instanceof \RainLoop\Exceptions\ClientException)
|
||||
{
|
||||
public function FalseResponse(int $iErrorCode = 0, string $sErrorMessage = '', string $sAdditionalErrorMessage = '') : array
|
||||
{
|
||||
return $this->DefaultResponse(false, [
|
||||
'ErrorCode' => $iErrorCode,
|
||||
'ErrorMessage' => $sErrorMessage,
|
||||
'ErrorMessageAdditional' => $sAdditionalErrorMessage
|
||||
]);
|
||||
}
|
||||
|
||||
public function ExceptionResponse(\Throwable $oException) : array
|
||||
{
|
||||
$iErrorCode = 0;
|
||||
$sErrorMessage = '';
|
||||
$sErrorMessageAdditional = '';
|
||||
|
||||
if ($oException instanceof \RainLoop\Exceptions\ClientException) {
|
||||
$iErrorCode = $oException->getCode();
|
||||
$sErrorMessage = null;
|
||||
|
||||
if ($iErrorCode === Notifications::ClientViewError)
|
||||
{
|
||||
if ($iErrorCode === Notifications::ClientViewError) {
|
||||
$sErrorMessage = $oException->getMessage();
|
||||
}
|
||||
|
||||
$sErrorMessageAdditional = $oException->getAdditionalMessage();
|
||||
if (empty($sErrorMessageAdditional))
|
||||
{
|
||||
$sErrorMessageAdditional = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$iErrorCode = Notifications::UnknownError;
|
||||
$sErrorMessage = $oException->getCode().' - '.$oException->getMessage();
|
||||
}
|
||||
|
||||
$oPrevious = $oException->getPrevious();
|
||||
if ($oPrevious)
|
||||
{
|
||||
$this->Logger()->WriteException($oPrevious);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->Logger()->WriteException($oException);
|
||||
}
|
||||
$this->logException($oException->getPrevious() ?: $oException);
|
||||
|
||||
return $this->FalseResponse($sActionName, $iErrorCode, $sErrorMessage, $sErrorMessageAdditional);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $mResult = false
|
||||
*/
|
||||
private function mainDefaultResponse(string $sActionName, $mResult = false, array $aAdditionalParams = array()) : array
|
||||
{
|
||||
$sActionName = 'Do' === \substr($sActionName, 0, 2) ? \substr($sActionName, 2) : $sActionName;
|
||||
$sActionName = \preg_replace('/[^a-zA-Z0-9_]+/', '', $sActionName);
|
||||
|
||||
$aResult = array(
|
||||
// 'Version' => APP_VERSION,
|
||||
'Action' => $sActionName,
|
||||
'Result' => $this->responseObject($mResult, $sActionName)
|
||||
);
|
||||
|
||||
foreach ($aAdditionalParams as $sKey => $mValue)
|
||||
{
|
||||
$aResult[$sKey] = $mValue;
|
||||
}
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
private function isFileHasThumbnail(string $sFileName) : bool
|
||||
{
|
||||
static $aCache = array();
|
||||
|
||||
$sExt = \MailSo\Base\Utils::GetFileExtension($sFileName);
|
||||
if (isset($aCache[$sExt])) {
|
||||
return $aCache[$sExt];
|
||||
}
|
||||
|
||||
$bResult = (
|
||||
\extension_loaded('gd')
|
||||
|| \extension_loaded('gmagick')
|
||||
|| \extension_loaded('imagick')
|
||||
) && \in_array($sExt, ['png', 'gif', 'jpg', 'jpeg']);
|
||||
|
||||
$aCache[$sExt] = $bResult;
|
||||
|
||||
return $bResult;
|
||||
return $this->DefaultResponse(false, [
|
||||
'ErrorCode' => $iErrorCode,
|
||||
'ErrorMessage' => $sErrorMessage,
|
||||
'ErrorMessageAdditional' => $sErrorMessageAdditional
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -141,124 +77,36 @@ trait Response
|
|||
private $aCheckableFolder = null;
|
||||
private function responseObject($mResponse, string $sParent = '')
|
||||
{
|
||||
if (!($mResponse instanceof \JsonSerializable))
|
||||
{
|
||||
if (\is_object($mResponse))
|
||||
{
|
||||
if (!($mResponse instanceof \JsonSerializable)) {
|
||||
if (\is_object($mResponse)) {
|
||||
return '["'.\get_class($mResponse).'"]';
|
||||
}
|
||||
|
||||
if (\is_array($mResponse))
|
||||
{
|
||||
foreach ($mResponse as $iKey => $oItem)
|
||||
{
|
||||
$mResponse[$iKey] = $this->responseObject($oItem, $sParent);
|
||||
if (\is_array($mResponse)) {
|
||||
foreach ($mResponse as $iKey => $oItem) {
|
||||
$mResponse[$iKey] = $this->responseObject($oItem, 'Array');
|
||||
}
|
||||
}
|
||||
|
||||
return $mResponse;
|
||||
}
|
||||
|
||||
if ($mResponse instanceof \MailSo\Mail\Message)
|
||||
{
|
||||
$mResult = $mResponse->jsonSerialize();
|
||||
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
if (!$mResult['DateTimeStampInUTC'] || $this->Config()->Get('labs', 'date_from_headers', false)) {
|
||||
$iDateTimeStampInUTC = $mResponse->HeaderTimeStampInUTC();
|
||||
if ($iDateTimeStampInUTC) {
|
||||
$mResult['DateTimeStampInUTC'] = $iDateTimeStampInUTC;
|
||||
}
|
||||
}
|
||||
|
||||
// \MailSo\Mime\EmailCollection
|
||||
foreach (['ReplyTo','From','To','Cc','Bcc','Sender','DeliveredTo'] as $prop) {
|
||||
$mResult[$prop] = $this->responseObject($mResult[$prop], $sParent);
|
||||
}
|
||||
|
||||
$sSubject = $mResult['subject'];
|
||||
$mResult['Hash'] = \md5($mResult['Folder'].$mResult['Uid']);
|
||||
$mResult['RequestHash'] = Utils::EncodeKeyValuesQ(array(
|
||||
'Account' => $oAccount->Hash(),
|
||||
'Folder' => $mResult['Folder'],
|
||||
'Uid' => $mResult['Uid'],
|
||||
'MimeType' => 'message/rfc822',
|
||||
'FileName' => (\strlen($sSubject) ? \MailSo\Base\Utils::SecureFileName($sSubject) : 'message-'.$mResult['Uid']) . '.eml'
|
||||
));
|
||||
|
||||
$mResult['Attachments'] = $this->responseObject($mResponse->Attachments(), $sParent);
|
||||
|
||||
if ('Message' === $sParent)
|
||||
{
|
||||
$mResult['DraftInfo'] = $mResponse->DraftInfo();
|
||||
$mResult['InReplyTo'] = $mResponse->InReplyTo();
|
||||
$mResult['UnsubsribeLinks'] = $mResponse->UnsubsribeLinks();
|
||||
$mResult['References'] = $mResponse->References();
|
||||
|
||||
$mResult['Html'] = $mResponse->Html();
|
||||
$mResult['Plain'] = $mResponse->Plain();
|
||||
|
||||
// $this->GetCapa(Capa::OPEN_PGP) || $this->GetCapa(Capa::GNUPG)
|
||||
$mResult['PgpSigned'] = $mResponse->PgpSigned();
|
||||
$mResult['PgpEncrypted'] = $mResponse->PgpEncrypted();
|
||||
|
||||
$mResult['ReadReceipt'] = $mResponse->ReadReceipt();
|
||||
|
||||
if (\strlen($mResult['ReadReceipt']) && !\in_array('$forwarded', $mResult['Flags']))
|
||||
{
|
||||
// \in_array('$mdnsent', $mResult['Flags'])
|
||||
if (\strlen($mResult['ReadReceipt']))
|
||||
{
|
||||
try
|
||||
{
|
||||
$oReadReceipt = \MailSo\Mime\Email::Parse($mResult['ReadReceipt']);
|
||||
if (!$oReadReceipt)
|
||||
{
|
||||
$mResult['ReadReceipt'] = '';
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oException) { unset($oException); }
|
||||
}
|
||||
|
||||
if (\strlen($mResult['ReadReceipt']) && '1' === $this->Cacher($oAccount)->Get(
|
||||
\RainLoop\KeyPathHelper::ReadReceiptCache($oAccount->Email(), $mResult['Folder'], $mResult['Uid']), '0'))
|
||||
{
|
||||
$mResult['ReadReceipt'] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
return $mResult;
|
||||
}
|
||||
|
||||
if ($mResponse instanceof \MailSo\Mail\Attachment)
|
||||
{
|
||||
$mResult = $mResponse->jsonSerialize();
|
||||
$mResult['IsThumbnail'] = $this->GetCapa(Capa::ATTACHMENT_THUMBNAILS) && $this->isFileHasThumbnail($mResult['FileName']);
|
||||
$mResult['Download'] = Utils::EncodeKeyValuesQ(array(
|
||||
'Account' => $this->getAccountFromToken()->Hash(),
|
||||
'Folder' => $mResult['Folder'],
|
||||
'Uid' => $mResult['Uid'],
|
||||
'MimeIndex' => $mResult['MimeIndex'],
|
||||
'MimeType' => $mResult['MimeType'],
|
||||
'FileName' => $mResult['FileName']
|
||||
));
|
||||
return $mResult;
|
||||
}
|
||||
|
||||
if ($mResponse instanceof \MailSo\Mail\Folder)
|
||||
{
|
||||
if ($mResponse instanceof \MailSo\Mail\Message) {
|
||||
$aResult = $mResponse->jsonSerialize();
|
||||
|
||||
$aStatus = $mResponse->Status();
|
||||
if ($aStatus && isset($aStatus['MESSAGES'], $aStatus['UIDNEXT'])) {
|
||||
$aResult['Hash'] = $this->MailClient()->GenerateFolderHash(
|
||||
$mResponse->FullName(),
|
||||
$aStatus['MESSAGES'],
|
||||
$aStatus['UIDNEXT'],
|
||||
empty($aStatus['HIGHESTMODSEQ']) ? 0 : $aStatus['HIGHESTMODSEQ']
|
||||
);
|
||||
if (!$this->Config()->Get('labs', 'date_from_headers', true) && $aResult['internalTimestamp']) {
|
||||
$aResult['dateTimestamp'] = $aResult['internalTimestamp'];
|
||||
}
|
||||
if (!$sParent && \strlen($aResult['readReceipt']) && !\in_array('$forwarded', $aResult['flags'])) {
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
if ('1' === $this->Cacher($oAccount)->Get(\RainLoop\KeyPathHelper::ReadReceiptCache($oAccount->Email(), $aResult['folder'], $aResult['uid']), '0')) {
|
||||
$aResult['readReceipt'] = '';
|
||||
}
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
if ($mResponse instanceof \MailSo\Imap\Folder) {
|
||||
$aResult = $mResponse->jsonSerialize();
|
||||
|
||||
if (null === $this->aCheckableFolder) {
|
||||
$aCheckable = \json_decode(
|
||||
|
|
@ -268,27 +116,11 @@ trait Response
|
|||
);
|
||||
$this->aCheckableFolder = \is_array($aCheckable) ? $aCheckable : array();
|
||||
}
|
||||
$aResult['Checkable'] = \in_array($mResponse->FullName(), $this->aCheckableFolder);
|
||||
$aResult['checkable'] = \in_array($mResponse->FullName, $this->aCheckableFolder);
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
if ($mResponse instanceof \MailSo\Base\Collection)
|
||||
{
|
||||
$mResult = $mResponse->jsonSerialize();
|
||||
$mResult['@Collection'] = $this->responseObject($mResult['@Collection'], $sParent);
|
||||
if ($mResponse instanceof \MailSo\Mail\EmailCollection) {
|
||||
return \array_slice($mResult['@Collection'], 0, 100);
|
||||
}
|
||||
if ($mResponse instanceof \MailSo\Mail\AttachmentCollection
|
||||
|| $mResponse instanceof \MailSo\Mail\FolderCollection
|
||||
|| $mResponse instanceof \MailSo\Mail\MessageCollection
|
||||
) {
|
||||
return $mResult;
|
||||
}
|
||||
return $mResult['@Collection'];
|
||||
}
|
||||
|
||||
return $mResponse;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ trait Themes
|
|||
&& ($oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount))) {
|
||||
$sTheme = (string) $oSettingsLocal->GetConf('Theme', $sTheme);
|
||||
}
|
||||
$sTheme = $this->ValidateTheme($sTheme) ?: 'Default';
|
||||
$sTheme = $this->ValidateTheme($sTheme);
|
||||
}
|
||||
return $sTheme;
|
||||
}
|
||||
|
|
@ -54,16 +54,24 @@ trait Themes
|
|||
}
|
||||
|
||||
$sDir = APP_INDEX_ROOT_PATH . 'themes'; // custom user themes
|
||||
if (\is_dir($sDir)) {
|
||||
$rDirH = \opendir($sDir);
|
||||
if ($rDirH) {
|
||||
if (\is_dir($sDir) && ($rDirH = \opendir($sDir))) {
|
||||
while (($sFile = \readdir($rDirH)) !== false) {
|
||||
if ('.' !== $sFile[0] && \is_dir($sDir . '/' . $sFile)
|
||||
&& (\file_exists("{$sDir}/{$sFile}/styles.css") || \file_exists("{$sDir}/{$sFile}/styles.less"))) {
|
||||
$aCache[] = $sFile . '@custom';
|
||||
}
|
||||
}
|
||||
\closedir($rDirH);
|
||||
}
|
||||
|
||||
if (\class_exists('OC', false)) {
|
||||
$sDir = \OC::$SERVERROOT . '/themes'; // custom user themes
|
||||
if (\is_dir($sDir) && ($rDirH = \opendir($sDir))) {
|
||||
while (($sFile = \readdir($rDirH)) !== false) {
|
||||
if ('.' !== $sFile[0] && \is_dir($sDir . '/' . $sFile)
|
||||
&& (\file_exists("{$sDir}/{$sFile}/styles.css") || \file_exists("{$sDir}/{$sFile}/styles.less"))) {
|
||||
$aCache[] = $sFile . '@custom';
|
||||
if ('.' !== $sFile[0] && \is_dir("{$sDir}/{$sFile}") && \file_exists("{$sDir}/{$sFile}/snappymail/style.css")) {
|
||||
$aCache[] = $sFile . '@nextcloud';
|
||||
}
|
||||
}
|
||||
|
||||
\closedir($rDirH);
|
||||
}
|
||||
}
|
||||
|
|
@ -82,43 +90,49 @@ trait Themes
|
|||
return $aCache;
|
||||
}
|
||||
|
||||
public function ThemeLink(bool $bAdmin): string
|
||||
{
|
||||
return './?/Css/0/' . ($bAdmin ? 'Admin' : 'User') . '/-/' . $this->GetTheme($bAdmin) . '/-/' . $this->StaticCache() . '/Hash/-/';
|
||||
}
|
||||
|
||||
public function ValidateTheme(string $sTheme): string
|
||||
{
|
||||
return \in_array($sTheme, $this->GetThemes()) ? $sTheme : $this->Config()->Get('themes', 'default', 'Default');
|
||||
}
|
||||
|
||||
public function compileCss(string $sTheme, bool $bAdmin) : string
|
||||
{
|
||||
$bCustomTheme = '@custom' === \substr($sTheme, -7);
|
||||
if ($bCustomTheme) {
|
||||
$sTheme = \substr($sTheme, 0, -7);
|
||||
}
|
||||
|
||||
$mResult = array();
|
||||
|
||||
$sBase = ($bCustomTheme ? \RainLoop\Utils::WebPath() : \RainLoop\Utils::WebVersionPath())
|
||||
. "themes/{$sTheme}/";
|
||||
|
||||
$bLess = false;
|
||||
|
||||
$sThemeCSSFile = ($bCustomTheme ? APP_INDEX_ROOT_PATH : APP_VERSION_ROOT_PATH).'themes/'.$sTheme.'/styles.css';
|
||||
if (\is_file($sThemeCSSFile)) {
|
||||
$mResult[] = \file_get_contents($sThemeCSSFile);
|
||||
} else {
|
||||
$sThemeCSSFile = \str_replace('styles.css', 'styles.less', $sThemeCSSFile);
|
||||
if (\is_file($sThemeCSSFile)) {
|
||||
$bLess = true;
|
||||
$mResult[] = "@base: \"{$sBase}\";";
|
||||
$mResult[] = \file_get_contents($sThemeCSSFile);
|
||||
if (!\in_array($sTheme, $this->GetThemes())) {
|
||||
$sTheme = $this->Config()->Get('webmail', 'theme', 'Default');
|
||||
if (!\in_array($sTheme, $this->GetThemes())) {
|
||||
$sTheme = 'Default';
|
||||
}
|
||||
}
|
||||
return $sTheme;
|
||||
}
|
||||
|
||||
$mResult[] = $this->Plugins()->CompileCss($bAdmin, $bLess);
|
||||
public function compileCss(string $sTheme, bool $bAdmin, bool $bMinified = false) : string
|
||||
{
|
||||
$mResult = array();
|
||||
$bLess = false;
|
||||
|
||||
if ('@nextcloud' === \substr($sTheme, -10)) {
|
||||
$sBase = \OC::$WEBROOT . '/';
|
||||
$sThemeCSSFile = \OC::$SERVERROOT . '/themes/' . \str_replace('@nextcloud', '/snappymail/style.css', $sTheme);
|
||||
} else {
|
||||
$bCustomTheme = '@custom' === \substr($sTheme, -7);
|
||||
if ($bCustomTheme) {
|
||||
$sTheme = \substr($sTheme, 0, -7);
|
||||
$sBase = \RainLoop\Utils::WebPath();
|
||||
} else {
|
||||
$sBase = \RainLoop\Utils::WebVersionPath();
|
||||
}
|
||||
$sBase .= "themes/{$sTheme}/";
|
||||
$sThemeCSSFile = ($bCustomTheme ? APP_INDEX_ROOT_PATH : APP_VERSION_ROOT_PATH).'themes/'.$sTheme.'/styles.css';
|
||||
if (!\is_file($sThemeCSSFile)) {
|
||||
$sThemeCSSFile = \str_replace('styles.css', 'styles.less', $sThemeCSSFile);
|
||||
if (\is_file($sThemeCSSFile)) {
|
||||
$bLess = true;
|
||||
$mResult[] = "@base: \"{$sBase}\";";
|
||||
$mResult[] = \file_get_contents($sThemeCSSFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (\is_file($sThemeCSSFile)) {
|
||||
$mResult[] = \file_get_contents($sThemeCSSFile);
|
||||
}
|
||||
|
||||
$mResult[] = $this->Plugins()->CompileCss($bAdmin, $bLess, $bMinified);
|
||||
|
||||
$mResult = \preg_replace('@(url\(["\']?)(\\./)?([a-z]+[^:a-z])@',
|
||||
"\$1{$sBase}\$3",
|
||||
|
|
@ -127,4 +141,84 @@ trait Themes
|
|||
return $bLess ? (new \LessPHP\lessc())->compile($mResult) : $mResult;
|
||||
// : \str_replace(';}', '}', \preg_replace('/\\s*([:;{},])\\s*/', '\1', \preg_replace('/\\s+/', ' ', \preg_replace('#/\\*.*?\\*/#s', '', $mResult))));
|
||||
}
|
||||
|
||||
public function UploadBackground(?array $aFile, int $iError): array
|
||||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
if (!$this->GetCapa(\RainLoop\Enumerations\Capa::USER_BACKGROUND)) {
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
$sName = '';
|
||||
$sHash = '';
|
||||
|
||||
if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) {
|
||||
$sMimeType = \SnappyMail\File\MimeType::fromFile($aFile['tmp_name'], $aFile['name'])
|
||||
?: \SnappyMail\File\MimeType::fromFilename($aFile['name'])
|
||||
?: $aFile['type'];
|
||||
if (\in_array($sMimeType, array('image/png', 'image/jpg', 'image/jpeg', 'image/webp'))) {
|
||||
$sSavedName = 'upload-post-' . \md5($aFile['name'] . $aFile['tmp_name'])
|
||||
. \SnappyMail\File\MimeType::toExtension($sMimeType);
|
||||
if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) {
|
||||
$iError = \RainLoop\Enumerations\UploadError::ON_SAVING;
|
||||
} else {
|
||||
$rData = $this->FilesProvider()->GetFile($oAccount, $sSavedName);
|
||||
if (\is_resource($rData)) {
|
||||
$sData = \stream_get_contents($rData);
|
||||
if (!empty($sData) && \strlen($sData)) {
|
||||
$sName = $aFile['name'];
|
||||
if (empty($sName)) {
|
||||
$sName = '_';
|
||||
}
|
||||
|
||||
if ($this->StorageProvider()->Put($oAccount,
|
||||
\RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
|
||||
'background',
|
||||
// Used by RawUserBackground()
|
||||
\RainLoop\Utils::jsonEncode(array(
|
||||
'ContentType' => $sMimeType,
|
||||
'Raw' => \base64_encode($sData)
|
||||
))
|
||||
)) {
|
||||
$oSettings = $this->SettingsProvider()->Load($oAccount);
|
||||
if ($oSettings) {
|
||||
$sHash = \MailSo\Base\Utils::Sha1Rand($sName . APP_VERSION . APP_SALT);
|
||||
|
||||
$oSettings->SetConf('UserBackgroundName', $sName);
|
||||
$oSettings->SetConf('UserBackgroundHash', $sHash);
|
||||
$this->SettingsProvider()->Save($oAccount, $oSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($sData);
|
||||
}
|
||||
|
||||
if (\is_resource($rData)) {
|
||||
\fclose($rData);
|
||||
}
|
||||
|
||||
unset($rData);
|
||||
}
|
||||
|
||||
$this->FilesProvider()->Clear($oAccount, $sSavedName);
|
||||
} else {
|
||||
$iError = \RainLoop\Enumerations\UploadError::FILE_TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
if (UPLOAD_ERR_OK !== $iError) {
|
||||
$iClientError = 0;
|
||||
$sError = \RainLoop\Enumerations\UploadError::getUserMessage($iError, $iClientError);
|
||||
if (!empty($sError)) {
|
||||
return $this->FalseResponse($iClientError, $sError);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(!empty($sName) && !empty($sHash) ? array(
|
||||
'name' => $sName,
|
||||
'hash' => $sHash
|
||||
) : false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,18 +15,15 @@ trait User
|
|||
use Filters;
|
||||
use Folders;
|
||||
use Messages;
|
||||
use Attachments;
|
||||
use Pgp;
|
||||
|
||||
/**
|
||||
* @var \RainLoop\Providers\Suggestions
|
||||
*/
|
||||
private $oSuggestionsProvider = null;
|
||||
private ?Suggestions $oSuggestionsProvider = null;
|
||||
|
||||
public function SuggestionsProvider(): Suggestions
|
||||
{
|
||||
if (null === $this->oSuggestionsProvider) {
|
||||
$this->oSuggestionsProvider = new Suggestions(
|
||||
$this->fabrica('suggestions'));
|
||||
$this->oSuggestionsProvider = new Suggestions($this->fabrica('suggestions'));
|
||||
}
|
||||
|
||||
return $this->oSuggestionsProvider;
|
||||
|
|
@ -39,177 +36,36 @@ trait User
|
|||
{
|
||||
$sEmail = \MailSo\Base\Utils::Trim($this->GetActionParam('Email', ''));
|
||||
$sPassword = $this->GetActionParam('Password', '');
|
||||
$bSignMe = !empty($this->GetActionParam('SignMe', 0));
|
||||
$bSignMe = !empty($this->GetActionParam('signMe', 0));
|
||||
|
||||
$this->Logger()->AddSecret($sPassword);
|
||||
$this->logMask($sPassword);
|
||||
|
||||
$oAccount = $this->LoginProcess($sEmail, $sPassword, $bSignMe);
|
||||
$this->Plugins()->RunHook('login.success', array($oAccount));
|
||||
try {
|
||||
$oAccount = $this->LoginProcess($sEmail, $sPassword, $bSignMe);
|
||||
} catch (\Throwable $oException) {
|
||||
$this->loginErrorDelay();
|
||||
throw $oException;
|
||||
}
|
||||
|
||||
$this->SetAuthToken($oAccount);
|
||||
|
||||
$sLanguage = $this->GetActionParam('Language', '');
|
||||
if ($oAccount && $sLanguage)
|
||||
{
|
||||
$oSettings = $this->SettingsProvider()->Load($oAccount);
|
||||
if ($oSettings)
|
||||
{
|
||||
$sLanguage = $this->ValidateLanguage($sLanguage);
|
||||
$sCurrentLanguage = $oSettings->GetConf('Language', '');
|
||||
$this->Plugins()->RunHook('login.success', array($oAccount));
|
||||
|
||||
if ($sCurrentLanguage !== $sLanguage)
|
||||
{
|
||||
$oSettings->SetConf('Language', $sLanguage);
|
||||
$sLanguage = $this->GetActionParam('language', '');
|
||||
if ($oAccount && $sLanguage) {
|
||||
$oSettings = $this->SettingsProvider()->Load($oAccount);
|
||||
if ($oSettings) {
|
||||
$sLanguage = $this->ValidateLanguage($sLanguage);
|
||||
$sCurrentLanguage = $oSettings->GetConf('language', '');
|
||||
|
||||
if ($sCurrentLanguage !== $sLanguage) {
|
||||
$oSettings->SetConf('language', $sLanguage);
|
||||
$this->SettingsProvider()->Save($oAccount, $oSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $this->AppData(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \MailSo\RuntimeException
|
||||
*/
|
||||
public function DoAttachmentsActions() : array
|
||||
{
|
||||
if (!$this->GetCapa(Capa::ATTACHMENTS_ACTIONS))
|
||||
{
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
}
|
||||
|
||||
$oAccount = $this->initMailClientConnection();
|
||||
|
||||
$sAction = $this->GetActionParam('Do', '');
|
||||
$aHashes = $this->GetActionParam('Hashes', null);
|
||||
|
||||
$mResult = false;
|
||||
$bError = false;
|
||||
$aData = false;
|
||||
|
||||
if (\is_array($aHashes) && \count($aHashes))
|
||||
{
|
||||
$aData = array();
|
||||
foreach ($aHashes as $sZipHash)
|
||||
{
|
||||
$aResult = $this->getMimeFileByHash($oAccount, $sZipHash);
|
||||
if (!empty($aResult['FileHash']))
|
||||
{
|
||||
$aData[] = $aResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
$bError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$oFilesProvider = $this->FilesProvider();
|
||||
if (!empty($sAction) && !$bError && \is_array($aData) && \count($aData) &&
|
||||
$oFilesProvider && $oFilesProvider->IsActive())
|
||||
{
|
||||
$bError = false;
|
||||
switch (\strtolower($sAction))
|
||||
{
|
||||
case 'zip':
|
||||
|
||||
$sZipHash = \MailSo\Base\Utils::Sha1Rand();
|
||||
$sZipFileName = $oFilesProvider->GenerateLocalFullFileName($oAccount, $sZipHash);
|
||||
|
||||
if (!empty($sZipFileName)) {
|
||||
if (\class_exists('ZipArchive')) {
|
||||
$oZip = new \ZipArchive();
|
||||
$oZip->open($sZipFileName, \ZIPARCHIVE::CREATE | \ZIPARCHIVE::OVERWRITE);
|
||||
$oZip->setArchiveComment('SnappyMail/'.APP_VERSION);
|
||||
foreach ($aData as $aItem) {
|
||||
$sFileName = (string) (isset($aItem['FileName']) ? $aItem['FileName'] : 'file.dat');
|
||||
$sFileHash = (string) (isset($aItem['FileHash']) ? $aItem['FileHash'] : '');
|
||||
if (!empty($sFileHash)) {
|
||||
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash);
|
||||
if (!$oZip->addFile($sFullFileNameHash, $sFileName)) {
|
||||
$bError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bError) {
|
||||
$bError = !$oZip->close();
|
||||
} else {
|
||||
$oZip->close();
|
||||
}
|
||||
/*
|
||||
} else {
|
||||
@\unlink($sZipFileName);
|
||||
$oZip = new \SnappyMail\Stream\ZIP($sZipFileName);
|
||||
// $oZip->setArchiveComment('SnappyMail/'.APP_VERSION);
|
||||
foreach ($aData as $aItem) {
|
||||
$sFileName = (string) (isset($aItem['FileName']) ? $aItem['FileName'] : 'file.dat');
|
||||
$sFileHash = (string) (isset($aItem['FileHash']) ? $aItem['FileHash'] : '');
|
||||
if (!empty($sFileHash)) {
|
||||
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash);
|
||||
if (!$oZip->addFile($sFullFileNameHash, $sFileName)) {
|
||||
$bError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$oZip->close();
|
||||
*/
|
||||
} else {
|
||||
@\unlink($sZipFileName);
|
||||
$oZip = new \PharData($sZipFileName . '.zip', 0, null, \Phar::ZIP);
|
||||
$oZip->compressFiles(\Phar::GZ);
|
||||
foreach ($aData as $aItem) {
|
||||
$sFileName = (isset($aItem['FileName']) ? (string) $aItem['FileName'] : 'file.dat');
|
||||
$sFileHash = (isset($aItem['FileHash']) ? (string) $aItem['FileHash'] : '');
|
||||
if ($sFileHash) {
|
||||
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash);
|
||||
$oZip->addFile($sFullFileNameHash, $sFileName);
|
||||
}
|
||||
}
|
||||
$oZip->compressFiles(\Phar::GZ);
|
||||
unset($oZip);
|
||||
\rename($sZipFileName . '.zip', $sZipFileName);
|
||||
}
|
||||
|
||||
foreach ($aData as $aItem) {
|
||||
$sFileHash = (isset($aItem['FileHash']) ? (string) $aItem['FileHash'] : '');
|
||||
if ($sFileHash) {
|
||||
$oFilesProvider->Clear($oAccount, $sFileHash);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bError) {
|
||||
$mResult = array(
|
||||
'FileHash' => Utils::EncodeKeyValuesQ(array(
|
||||
'Account' => $oAccount ? $oAccount->Hash() : '',
|
||||
'FileName' => 'attachments.zip',
|
||||
'MimeType' => 'application/zip',
|
||||
'FileHash' => $sZipHash
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$data = new \SnappyMail\AttachmentsAction;
|
||||
$data->action = $sAction;
|
||||
$data->items = $aData;
|
||||
$data->filesProvider = $oFilesProvider;
|
||||
$data->account = $oAccount;
|
||||
$this->Plugins()->RunHook('json.attachments', array($data));
|
||||
$mResult = $data->result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$bError = true;
|
||||
}
|
||||
|
||||
$this->requestSleep();
|
||||
return $this->DefaultResponse(__FUNCTION__, $bError ? false : $mResult);
|
||||
return $this->DefaultResponse($this->AppData(false));
|
||||
}
|
||||
|
||||
public function DoLogout() : array
|
||||
|
|
@ -217,7 +73,7 @@ trait User
|
|||
$bMain = true; // empty($_COOKIE[self::AUTH_ADDITIONAL_TOKEN_KEY]);
|
||||
$this->Logout($bMain);
|
||||
$bMain && $this->ClearSignMeData();
|
||||
return $this->TrueResponse(__FUNCTION__);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
|
||||
public function DoAppDelayStart() : array
|
||||
|
|
@ -240,20 +96,17 @@ trait User
|
|||
$iMainCacheTime = !empty($aTimers[0]) && \is_numeric($aTimers[0]) ? (int) $aTimers[0] : 0;
|
||||
$iFilesCacheTime = !empty($aTimers[1]) && \is_numeric($aTimers[1]) ? (int) $aTimers[1] : 0;
|
||||
|
||||
if (0 === $iMainCacheTime || $iMainCacheTime + $iOneDay1 < \time())
|
||||
{
|
||||
if (0 === $iMainCacheTime || $iMainCacheTime + $iOneDay1 < \time()) {
|
||||
$bMainCache = true;
|
||||
$iMainCacheTime = \time();
|
||||
}
|
||||
|
||||
if (0 === $iFilesCacheTime || $iFilesCacheTime + $iOneDay2 < \time())
|
||||
{
|
||||
if (0 === $iFilesCacheTime || $iFilesCacheTime + $iOneDay2 < \time()) {
|
||||
$bFilesCache = true;
|
||||
$iFilesCacheTime = \time();
|
||||
}
|
||||
|
||||
if ($bMainCache || $bFilesCache)
|
||||
{
|
||||
if ($bMainCache || $bFilesCache) {
|
||||
if (!$this->StorageProvider()->Put(null,
|
||||
\RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, 'Cache/Timers',
|
||||
\implode(',', array($iMainCacheTime, $iFilesCacheTime))))
|
||||
|
|
@ -262,26 +115,23 @@ trait User
|
|||
}
|
||||
}
|
||||
|
||||
if ($bMainCache)
|
||||
{
|
||||
$this->Logger()->Write('Cacher GC: Begin');
|
||||
if ($bMainCache) {
|
||||
$this->logWrite('Cacher GC: Begin');
|
||||
$this->Cacher()->GC(48);
|
||||
$this->Logger()->Write('Cacher GC: End');
|
||||
$this->logWrite('Cacher GC: End');
|
||||
|
||||
$this->Logger()->Write('Storage GC: Begin');
|
||||
$this->logWrite('Storage GC: Begin');
|
||||
$this->StorageProvider()->GC();
|
||||
$this->Logger()->Write('Storage GC: End');
|
||||
}
|
||||
else if ($bFilesCache)
|
||||
{
|
||||
$this->Logger()->Write('Files GC: Begin');
|
||||
$this->logWrite('Storage GC: End');
|
||||
} else if ($bFilesCache) {
|
||||
$this->logWrite('Files GC: Begin');
|
||||
$this->FilesProvider()->GC(48);
|
||||
$this->Logger()->Write('Files GC: End');
|
||||
$this->logWrite('Files GC: End');
|
||||
}
|
||||
|
||||
$this->Plugins()->RunHook('service.app-delay-start-end');
|
||||
|
||||
return $this->TrueResponse(__FUNCTION__);
|
||||
return $this->TrueResponse();
|
||||
}
|
||||
|
||||
public function DoSettingsUpdate() : array
|
||||
|
|
@ -294,28 +144,23 @@ trait User
|
|||
$oSettings = $this->SettingsProvider()->Load($oAccount);
|
||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||
|
||||
if ($oConfig->Get('webmail', 'allow_languages_on_settings', true))
|
||||
{
|
||||
$this->setSettingsFromParams($oSettings, 'Language', 'string', function ($sLanguage) use ($self) {
|
||||
if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)) {
|
||||
$this->setSettingsFromParams($oSettings, 'language', 'string', function ($sLanguage) use ($self) {
|
||||
return $self->ValidateLanguage($sLanguage);
|
||||
});
|
||||
} else {
|
||||
// $oSettings->SetConf('language', $this->ValidateLanguage($oConfig->Get('webmail', 'language', 'en')));
|
||||
}
|
||||
else
|
||||
{
|
||||
// $oSettings->SetConf('Language', $this->ValidateLanguage($oConfig->Get('webmail', 'language', 'en')));
|
||||
}
|
||||
$this->setSettingsFromParams($oSettings, 'hourCycle', 'string');
|
||||
|
||||
if ($this->GetCapa(Capa::THEMES))
|
||||
{
|
||||
if ($this->GetCapa(Capa::THEMES)) {
|
||||
$this->setSettingsFromParams($oSettingsLocal, 'Theme', 'string', function ($sTheme) use ($self) {
|
||||
return $self->ValidateTheme($sTheme);
|
||||
});
|
||||
$this->setSettingsFromParams($oSettings, 'fontSansSerif', 'string');
|
||||
$this->setSettingsFromParams($oSettings, 'fontSerif', 'string');
|
||||
$this->setSettingsFromParams($oSettings, 'fontMono', 'string');
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// $oSettingsLocal->SetConf('Theme', $this->ValidateTheme($oConfig->Get('webmail', 'theme', 'Default')));
|
||||
}
|
||||
|
||||
|
|
@ -332,13 +177,21 @@ trait User
|
|||
$this->setSettingsFromParams($oSettings, 'EditorDefaultType', 'string');
|
||||
$this->setSettingsFromParams($oSettings, 'requestReadReceipt', 'bool');
|
||||
$this->setSettingsFromParams($oSettings, 'requestDsn', 'bool');
|
||||
$this->setSettingsFromParams($oSettings, 'requireTLS', 'bool');
|
||||
$this->setSettingsFromParams($oSettings, 'pgpSign', 'bool');
|
||||
$this->setSettingsFromParams($oSettings, 'pgpEncrypt', 'bool');
|
||||
$this->setSettingsFromParams($oSettings, 'allowSpellcheck', 'bool');
|
||||
|
||||
$this->setSettingsFromParams($oSettings, 'ViewHTML', 'bool');
|
||||
$this->setSettingsFromParams($oSettings, 'ShowImages', 'bool');
|
||||
$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');
|
||||
$this->setSettingsFromParams($oSettings, 'simpleAttachmentsList', 'bool');
|
||||
$this->setSettingsFromParams($oSettings, 'listGrouped', 'bool');
|
||||
$this->setSettingsFromParams($oSettings, 'ContactsAutosave', 'bool');
|
||||
$this->setSettingsFromParams($oSettings, 'DesktopNotifications', 'bool');
|
||||
$this->setSettingsFromParams($oSettings, 'SoundNotification', 'bool');
|
||||
|
|
@ -348,6 +201,7 @@ trait User
|
|||
$this->setSettingsFromParams($oSettings, 'AutoLogout', 'int');
|
||||
$this->setSettingsFromParams($oSettings, 'MessageReadDelay', 'int');
|
||||
$this->setSettingsFromParams($oSettings, 'MsgDefaultAction', 'int');
|
||||
$this->setSettingsFromParams($oSettings, 'showNextMessage', 'bool');
|
||||
|
||||
$this->setSettingsFromParams($oSettings, 'Resizer4Width', 'int');
|
||||
$this->setSettingsFromParams($oSettings, 'Resizer5Width', 'int');
|
||||
|
|
@ -358,9 +212,10 @@ trait User
|
|||
$this->setSettingsFromParams($oSettingsLocal, 'HideUnsubscribed', 'bool');
|
||||
$this->setSettingsFromParams($oSettingsLocal, 'HideDeleted', 'bool');
|
||||
$this->setSettingsFromParams($oSettingsLocal, 'UnhideKolabFolders', 'bool');
|
||||
$this->setSettingsFromParams($oSettingsLocal, 'ShowUnreadCount', 'bool');
|
||||
$this->setSettingsFromParams($oSettingsLocal, 'CheckMailInterval', 'int');
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__,
|
||||
$this->SettingsProvider()->Save($oAccount, $oSettings) &&
|
||||
return $this->DefaultResponse($this->SettingsProvider()->Save($oAccount, $oSettings) &&
|
||||
$this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal));
|
||||
}
|
||||
|
||||
|
|
@ -368,21 +223,20 @@ trait User
|
|||
{
|
||||
$oAccount = $this->initMailClientConnection();
|
||||
|
||||
if (!$this->GetCapa(Capa::QUOTA))
|
||||
{
|
||||
return $this->DefaultResponse(__FUNCTION__, array(0, 0, 0, 0));
|
||||
if (!$this->GetCapa(Capa::QUOTA)) {
|
||||
return $this->DefaultResponse(array(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$aQuota = $this->MailClient()->QuotaRoot();
|
||||
$aQuota = $this->ImapClient()->QuotaRoot();
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
throw new ClientException(Notifications::MailServerError, $oException);
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $aQuota);
|
||||
return $this->DefaultResponse($aQuota);
|
||||
}
|
||||
|
||||
public function DoSuggestions() : array
|
||||
|
|
@ -392,86 +246,27 @@ trait User
|
|||
$sQuery = \trim($this->GetActionParam('Query', ''));
|
||||
$iLimit = (int) $this->Config()->Get('contacts', 'suggestions_limit', 20);
|
||||
|
||||
$aResult = array();
|
||||
|
||||
$this->Plugins()->RunHook('json.suggestions-input-parameters', array(&$sQuery, &$iLimit, $oAccount));
|
||||
|
||||
$iLimit = (int) $iLimit;
|
||||
if (5 > $iLimit)
|
||||
{
|
||||
$iLimit = 5;
|
||||
$aResult = array();
|
||||
|
||||
if ($oSuggestionsProvider = $this->SuggestionsProvider()) {
|
||||
$aResult = $oSuggestionsProvider->Process($oAccount, $sQuery, $iLimit);
|
||||
}
|
||||
|
||||
$this->Plugins()->RunHook('json.suggestions-pre', array(&$aResult, $sQuery, $oAccount, $iLimit));
|
||||
|
||||
if ($iLimit > \count($aResult) && \strlen($sQuery))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Address Book
|
||||
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
||||
if ($oAddressBookProvider && $oAddressBookProvider->IsActive())
|
||||
{
|
||||
$aSuggestions = $oAddressBookProvider->GetSuggestions($sQuery, $iLimit);
|
||||
if (!\count($aResult))
|
||||
{
|
||||
$aResult = $aSuggestions;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResult = \array_merge($aResult, $aSuggestions);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$this->Logger()->WriteException($oException);
|
||||
}
|
||||
}
|
||||
|
||||
if ($iLimit > \count($aResult) && \strlen($sQuery))
|
||||
{
|
||||
$oSuggestionsProvider = $this->SuggestionsProvider();
|
||||
if ($oSuggestionsProvider && $oSuggestionsProvider->IsActive())
|
||||
{
|
||||
$aSuggestionsProviderResult = $oSuggestionsProvider->Process($oAccount, $sQuery, $iLimit);
|
||||
if (\is_array($aSuggestionsProviderResult) && \count($aSuggestionsProviderResult))
|
||||
{
|
||||
$aResult = \array_merge($aResult, $aSuggestionsProviderResult);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$aResult = Utils::RemoveSuggestionDuplicates($aResult);
|
||||
if ($iLimit < \count($aResult))
|
||||
{
|
||||
$aResult = \array_slice($aResult, 0, $iLimit);
|
||||
}
|
||||
|
||||
$this->Plugins()->RunHook('json.suggestions-post', array(&$aResult, $sQuery, $oAccount, $iLimit));
|
||||
|
||||
$aResult = Utils::RemoveSuggestionDuplicates($aResult);
|
||||
if ($iLimit < \count($aResult))
|
||||
{
|
||||
$aResult = \array_slice($aResult, 0, $iLimit);
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $aResult);
|
||||
return $this->DefaultResponse($aResult);
|
||||
}
|
||||
|
||||
public function DoClearUserBackground() : array
|
||||
{
|
||||
$oAccount = $this->getAccountFromToken();
|
||||
|
||||
if (!$this->GetCapa(Capa::USER_BACKGROUND))
|
||||
{
|
||||
return $this->FalseResponse(__FUNCTION__);
|
||||
if (!$this->GetCapa(Capa::USER_BACKGROUND)) {
|
||||
return $this->FalseResponse();
|
||||
}
|
||||
|
||||
$oSettings = $this->SettingsProvider()->Load($oAccount);
|
||||
if ($oAccount && $oSettings)
|
||||
{
|
||||
if ($oAccount && $oSettings) {
|
||||
$this->StorageProvider()->Clear($oAccount,
|
||||
\RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
|
||||
'background'
|
||||
|
|
@ -481,61 +276,13 @@ trait User
|
|||
$oSettings->SetConf('UserBackgroundHash', '');
|
||||
}
|
||||
|
||||
return $this->DefaultResponse(__FUNCTION__, $oAccount && $oSettings ?
|
||||
return $this->DefaultResponse($oAccount && $oSettings ?
|
||||
$this->SettingsProvider()->Save($oAccount, $oSettings) : false);
|
||||
}
|
||||
|
||||
private function getMimeFileByHash(\RainLoop\Model\Account $oAccount, string $sHash) : array
|
||||
{
|
||||
$aValues = $this->getDecodedRawKeyValue($sHash);
|
||||
|
||||
$sFolder = isset($aValues['Folder']) ? (string) $aValues['Folder'] : '';
|
||||
$iUid = isset($aValues['Uid']) ? (int) $aValues['Uid'] : 0;
|
||||
$sMimeIndex = isset($aValues['MimeIndex']) ? (string) $aValues['MimeIndex'] : '';
|
||||
|
||||
$sContentTypeIn = isset($aValues['MimeType']) ? (string) $aValues['MimeType'] : '';
|
||||
$sFileNameIn = isset($aValues['FileName']) ? (string) $aValues['FileName'] : '';
|
||||
|
||||
$oFileProvider = $this->FilesProvider();
|
||||
|
||||
$sResultHash = '';
|
||||
|
||||
$mResult = $this->MailClient()->MessageMimeStream(function ($rResource, $sContentType, $sFileName, $sMimeIndex = '')
|
||||
use ($oAccount, $oFileProvider, $sFileNameIn, $sContentTypeIn, &$sResultHash) {
|
||||
|
||||
unset($sContentType, $sFileName, $sMimeIndex);
|
||||
|
||||
if ($oAccount && \is_resource($rResource))
|
||||
{
|
||||
$sHash = \MailSo\Base\Utils::Sha1Rand($sFileNameIn.'~'.$sContentTypeIn);
|
||||
$rTempResource = $oFileProvider->GetFile($oAccount, $sHash, 'wb+');
|
||||
|
||||
if (\is_resource($rTempResource))
|
||||
{
|
||||
if (false !== \MailSo\Base\Utils::MultipleStreamWriter($rResource, array($rTempResource)))
|
||||
{
|
||||
$sResultHash = $sHash;
|
||||
}
|
||||
|
||||
\fclose($rTempResource);
|
||||
}
|
||||
}
|
||||
|
||||
}, $sFolder, $iUid, $sMimeIndex);
|
||||
|
||||
$aValues['FileHash'] = '';
|
||||
if ($mResult)
|
||||
{
|
||||
$aValues['FileHash'] = $sResultHash;
|
||||
}
|
||||
|
||||
return $aValues;
|
||||
}
|
||||
|
||||
private function setSettingsFromParams(\RainLoop\Settings $oSettings, string $sConfigName, string $sType = 'string', ?callable $cCallback = null) : void
|
||||
{
|
||||
if ($this->HasActionParam($sConfigName))
|
||||
{
|
||||
if ($this->HasActionParam($sConfigName)) {
|
||||
$sValue = $this->GetActionParam($sConfigName, '');
|
||||
switch ($sType)
|
||||
{
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue