From 0e3275599e6f3b6805b570614060156909ad4072 Mon Sep 17 00:00:00 2001 From: djmaze Date: Tue, 20 Oct 2020 12:23:53 +0200 Subject: [PATCH] More splitting of RainLoop/Actions.php --- .../0.0.0/app/libraries/RainLoop/Actions.php | 1524 +---------------- .../libraries/RainLoop/Actions/Accounts.php | 22 + .../app/libraries/RainLoop/Actions/Admin.php | 12 + .../libraries/RainLoop/Actions/Contacts.php | 86 + .../libraries/RainLoop/Actions/Filters.php | 12 + .../libraries/RainLoop/Actions/Folders.php | 157 ++ .../libraries/RainLoop/Actions/Messages.php | 457 ++++- .../app/libraries/RainLoop/Actions/Raw.php | 420 +++++ .../libraries/RainLoop/Actions/Response.php | 99 +- .../libraries/RainLoop/Actions/Templates.php | 84 +- .../libraries/RainLoop/Actions/TwoFactor.php | 123 +- .../app/libraries/RainLoop/Actions/User.php | 127 +- 12 files changed, 1524 insertions(+), 1599 deletions(-) create mode 100644 snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php index 95a09bf65..8a46f43ba 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -9,6 +9,7 @@ class Actions { use Actions\Admin; use Actions\User; + use Actions\Raw; use Actions\Response; const AUTH_TFA_SIGN_ME_TOKEN_KEY = 'rltfasmauth'; @@ -84,11 +85,6 @@ class Actions */ private $oLocalSettingsProvider; - /** - * @var \RainLoop\Providers\Filters - */ - private $oFiltersProvider; - /** * @var \RainLoop\Providers\AddressBook */ @@ -99,11 +95,6 @@ class Actions */ private $oSuggestionsProvider; - /** - * @var \RainLoop\Providers\TwoFactorAuth - */ - private $oTwoFactorAuthProvider; - /** * @var \RainLoop\Config\Application */ @@ -138,11 +129,9 @@ class Actions $this->oSettingsProvider = null; $this->oLocalSettingsProvider = null; $this->oFilesProvider = null; - $this->oFiltersProvider = null; $this->oDomainProvider = null; $this->oAddressBookProvider = null; $this->oSuggestionsProvider = null; - $this->oTwoFactorAuthProvider = null; $this->sSpecAuthToken = ''; $this->sUpdateAuthToken = ''; @@ -622,29 +611,6 @@ class Actions return $this->oMailClient; } - public function FiltersProvider() : Providers\Filters - { - if (null === $this->oFiltersProvider) - { - $this->oFiltersProvider = new Providers\Filters( - $this->fabrica('filters')); - } - - return $this->oFiltersProvider; - } - - public function TwoFactorAuthProvider() : Providers\TwoFactorAuth - { - if (null === $this->oTwoFactorAuthProvider) - { - $this->oTwoFactorAuthProvider = new Providers\TwoFactorAuth( - $this->Config()->Get('security', 'allow_two_factor_auth', false) ? $this->fabrica('two-factor-auth') : null - ); - } - - return $this->oTwoFactorAuthProvider; - } - public function StorageProvider(bool $bLocal = false) : Providers\Storage { if ($bLocal) @@ -1064,7 +1030,7 @@ class Actions { $oAccount = null; - $sSignMeToken = Utils::GetCookie(Actions::AUTH_SIGN_ME_TOKEN_KEY, ''); + $sSignMeToken = Utils::GetCookie(self::AUTH_SIGN_ME_TOKEN_KEY, ''); if (!empty($sSignMeToken)) { $aTokenData = Utils::DecodeKeyValuesQ($sSignMeToken); @@ -1089,7 +1055,7 @@ class Actions } else { - Utils::ClearCookie(Actions::AUTH_SIGN_ME_TOKEN_KEY); + Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY); } return $oAccount; @@ -1847,11 +1813,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut return $oAccount; } - private function generateSignMeToken(string $sEmail) : string - { - return \MailSo\Base\Utils::Md5Rand(APP_SALT.$sEmail); - } - public function GetAccounts(Model\Account $oAccount) : array { if ($this->GetCapa(false, false, Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) @@ -1906,71 +1867,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut return $aAccounts; } - public function GetTemplates(?Model\Account $oAccount) : array - { - $aTemplates = array(); - if ($oAccount) - { - $aData = array(); - - $sData = $this->StorageProvider(true)->Get($oAccount, - Providers\Storage\Enumerations\StorageType::CONFIG, - 'templates' - ); - - if ('' !== $sData && '[' === \substr($sData, 0, 1)) - { - $aData = \json_decode($sData, true); - } - - if (\is_array($aData) && 0 < \count($aData)) - { - foreach ($aData as $aItem) - { - $oItem = new Model\Template(); - $oItem->FromJSON($aItem); - - if ($oItem && $oItem->Validate()) - { - \array_push($aTemplates, $oItem); - } - } - } - - if (1 < \count($aTemplates)) - { - $sOrder = $this->StorageProvider()->Get($oAccount, - Providers\Storage\Enumerations\StorageType::CONFIG, - 'templates_order' - ); - - $aOrder = empty($sOrder) ? array() : \json_decode($sOrder, true); - if (\is_array($aOrder) && 1 < \count($aOrder)) - { - \usort($aTemplates, function ($a, $b) use ($aOrder) { - return \array_search($a->Id(), $aOrder) < \array_search($b->Id(), $aOrder) ? -1 : 1; - }); - } - } - } - - return $aTemplates; - } - - public function GetTemplateByID(Model\Account $oAccount, string $sID) : ?Model\Identity - { - $aTemplates = $this->GetTemplates($oAccount); - foreach ($aTemplates as $oIdentity) - { - if ($oIdentity && $sID === $oIdentity->Id()) - { - return $oIdentity; - } - } - - return isset($aTemplates[0]) ? $aTemplates[0] : null; - } - public function GetIdentities(Model\Account $oAccount) : array { $bAllowIdentities = $this->GetCapa(false, false, @@ -2068,11 +1964,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut return $bFirstOnEmpty && isset($aIdentities[0]) ? $aIdentities[0] : null; } - public function GetAccountIdentity(Model\Account $oAccount) : ?Model\Identity - { - return $this->GetIdentityByID($oAccount, '', true); - } - public function SetAccounts(Model\Account $oAccount, array $aAccounts = array()) : void { $sParentEmail = $oAccount->ParentEmailHelper(); @@ -2094,43 +1985,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut } } - public function SetIdentities(Model\Account $oAccount, array $aIdentities = array()) : bool - { - $bAllowIdentities = $this->GetCapa(false, false, Enumerations\Capa::IDENTITIES, $oAccount); - - $aResult = array(); - foreach ($aIdentities as $oItem) - { - if (!$bAllowIdentities && $oItem && !$oItem->IsAccountIdentities()) - { - continue; - } - - $aResult[] = $oItem->ToSimpleJSON(); - } - - return $this->StorageProvider(true)->Put($oAccount, - Providers\Storage\Enumerations\StorageType::CONFIG, - 'identities', - \json_encode($aResult) - ); - } - - public function SetTemplates(Model\Account $oAccount, array $aTemplates = array()) : array - { - $aResult = array(); - foreach ($aTemplates as $oItem) - { - $aResult[] = $oItem->ToSimpleJSON(); - } - - return $this->StorageProvider(true)->Put($oAccount, - Providers\Storage\Enumerations\StorageType::CONFIG, - 'templates', - \json_encode($aResult) - ); - } - /** * @throws \MailSo\Base\Exceptions\Exception */ @@ -2161,19 +2015,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut return $iResult; } - public function ClearSignMeData(Model\Account $oAccount) : void - { - if ($oAccount) - { - Utils::ClearCookie(Actions::AUTH_SIGN_ME_TOKEN_KEY); - - $this->StorageProvider()->Clear($oAccount, - Providers\Storage\Enumerations\StorageType::CONFIG, - 'sign_me' - ); - } - } - public function setConfigFromParams(Config\Application $oConfig, string $sParamName, string $sConfigSector, string $sConfigName, string $sType = 'string', ?callable $mStringCallback = null) : void { $sValue = $this->GetActionParam($sParamName, ''); @@ -2212,36 +2053,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut } } - private function setSettingsFromParams(Settings $oSettings, string $sConfigName, string $sType = 'string', ?callable $mStringCallback = null) : void - { - if ($this->HasActionParam($sConfigName)) - { - $sValue = $this->GetActionParam($sConfigName, ''); - switch ($sType) - { - default: - case 'string': - $sValue = (string) $sValue; - if ($mStringCallback && is_callable($mStringCallback)) - { - $sValue = call_user_func($mStringCallback, $sValue); - } - - $oSettings->SetConf($sConfigName, (string) $sValue); - break; - - case 'int': - $iValue = (int) $sValue; - $oSettings->SetConf($sConfigName, $iValue); - break; - - case 'bool': - $oSettings->SetConf($sConfigName, '1' === (string) $sValue); - break; - } - } - } - public function DoNoop() : array { $this->initMailClientConnection(); @@ -2259,704 +2070,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut APP_VERSION === (string) $this->GetActionParam('Version', '')); } - private function recFoldersNames(\MailSo\Mail\FolderCollection $oFolders) : array - { - $aResult = array(); - if ($oFolders) - { - foreach ($oFolders as $oFolder) - { - $aResult[] = $oFolder->FullNameRaw()."|". - implode("|", $oFolder->Flags()).($oFolder->IsSubscribed() ? '1' : '0'); - - $oSub = $oFolder->SubFolders(); - if ($oSub && 0 < $oSub->Count()) - { - $aResult = \array_merge($aResult, $this->recFoldersNames($oSub)); - } - } - } - - return $aResult; - } - - /** - * @staticvar array $aCache - */ - private function systemFoldersNames(Model\Account $oAccount) : array - { - static $aCache = null; - if (null === $aCache) - { - $aCache = array( - - 'Sent' => \MailSo\Imap\Enumerations\FolderType::SENT, - 'Send' => \MailSo\Imap\Enumerations\FolderType::SENT, - - 'Outbox' => \MailSo\Imap\Enumerations\FolderType::SENT, - 'Out box' => \MailSo\Imap\Enumerations\FolderType::SENT, - - 'Sent Item' => \MailSo\Imap\Enumerations\FolderType::SENT, - 'Sent Items' => \MailSo\Imap\Enumerations\FolderType::SENT, - 'Send Item' => \MailSo\Imap\Enumerations\FolderType::SENT, - 'Send Items' => \MailSo\Imap\Enumerations\FolderType::SENT, - 'Sent Mail' => \MailSo\Imap\Enumerations\FolderType::SENT, - 'Sent Mails' => \MailSo\Imap\Enumerations\FolderType::SENT, - 'Send Mail' => \MailSo\Imap\Enumerations\FolderType::SENT, - 'Send Mails' => \MailSo\Imap\Enumerations\FolderType::SENT, - - 'Drafts' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, - - 'Draft' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, - 'Draft Mail' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, - 'Draft Mails' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, - 'Drafts Mail' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, - 'Drafts Mails' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, - - 'Junk E-mail' => \MailSo\Imap\Enumerations\FolderType::JUNK, - - 'Spam' => \MailSo\Imap\Enumerations\FolderType::JUNK, - 'Spams' => \MailSo\Imap\Enumerations\FolderType::JUNK, - - 'Junk' => \MailSo\Imap\Enumerations\FolderType::JUNK, - 'Bulk Mail' => \MailSo\Imap\Enumerations\FolderType::JUNK, - 'Bulk Mails' => \MailSo\Imap\Enumerations\FolderType::JUNK, - - 'Deleted Items' => \MailSo\Imap\Enumerations\FolderType::TRASH, - - 'Trash' => \MailSo\Imap\Enumerations\FolderType::TRASH, - 'Deleted' => \MailSo\Imap\Enumerations\FolderType::TRASH, - 'Bin' => \MailSo\Imap\Enumerations\FolderType::TRASH, - - 'Archive' => \MailSo\Imap\Enumerations\FolderType::ALL, - 'Archives' => \MailSo\Imap\Enumerations\FolderType::ALL, - - 'All' => \MailSo\Imap\Enumerations\FolderType::ALL, - 'All Mail' => \MailSo\Imap\Enumerations\FolderType::ALL, - 'All Mails' => \MailSo\Imap\Enumerations\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; - } - - private function recFoldersTypes(Model\Account $oAccount, \MailSo\Mail\FolderCollection $oFolders, array &$aResult, bool $bListFolderTypes = true) : void - { - if ($oFolders && $oFolders->Count()) - { - if ($bListFolderTypes) - { - foreach ($oFolders as $oFolder) - { - $iFolderListType = $oFolder->GetFolderListType(); - if (!isset($aResult[$iFolderListType]) && \in_array($iFolderListType, array( - \MailSo\Imap\Enumerations\FolderType::INBOX, - \MailSo\Imap\Enumerations\FolderType::SENT, - \MailSo\Imap\Enumerations\FolderType::DRAFTS, - \MailSo\Imap\Enumerations\FolderType::JUNK, - \MailSo\Imap\Enumerations\FolderType::TRASH, - \MailSo\Imap\Enumerations\FolderType::ALL - ))) - { - $aResult[$iFolderListType] = $oFolder->FullNameRaw(); - } - } - - foreach ($oFolders as $oFolder) - { - $oSub = $oFolder->SubFolders(); - if ($oSub && 0 < $oSub->Count()) - { - $this->recFoldersTypes($oAccount, $oSub, $aResult, true); - } - } - } - - $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]) && \in_array($iFolderType, array( - \MailSo\Imap\Enumerations\FolderType::INBOX, - \MailSo\Imap\Enumerations\FolderType::SENT, - \MailSo\Imap\Enumerations\FolderType::DRAFTS, - \MailSo\Imap\Enumerations\FolderType::JUNK, - \MailSo\Imap\Enumerations\FolderType::TRASH, - \MailSo\Imap\Enumerations\FolderType::ALL - ))) - { - $aResult[$iFolderType] = $oFolder->FullNameRaw(); - } - } - } - - foreach ($oFolders as $oFolder) - { - $oSub = $oFolder->SubFolders(); - if ($oSub && 0 < $oSub->Count()) - { - $this->recFoldersTypes($oAccount, $oSub, $aResult, false); - } - } - } - } - - private function buildMessage(Model\Account $oAccount, bool $bWithDraftInfo = true) : \MailSo\Mime\Message - { - $sIdentityID = $this->GetActionParam('IdentityID', ''); - $sTo = $this->GetActionParam('To', ''); - $sCc = $this->GetActionParam('Cc', ''); - $sBcc = $this->GetActionParam('Bcc', ''); - $sReplyTo = $this->GetActionParam('ReplyTo', ''); - $sSubject = $this->GetActionParam('Subject', ''); - $bTextIsHtml = '1' === $this->GetActionParam('TextIsHtml', '0'); - $bReadReceiptRequest = '1' === $this->GetActionParam('ReadReceiptRequest', '0'); - $bMarkAsImportant = '1' === $this->GetActionParam('MarkAsImportant', '0'); - $sText = $this->GetActionParam('Text', ''); - $aAttachments = $this->GetActionParam('Attachments', null); - - $aDraftInfo = $this->GetActionParam('DraftInfo', null); - $sInReplyTo = $this->GetActionParam('InReplyTo', ''); - $sReferences = $this->GetActionParam('References', ''); - - $oMessage = new \MailSo\Mime\Message(); - - if (!$this->Config()->Get('security', 'hide_x_mailer_header', true)) - { - $oMessage->SetXMailer('SnappyMail/'.APP_VERSION); - } else { - $oMessage->DoesNotAddDefaultXMailer(); - } - - $oFromIdentity = $this->GetIdentityByID($oAccount, $sIdentityID); - if ($oFromIdentity) - { - $oMessage->SetFrom(new \MailSo\Mime\Email( - $oFromIdentity->Email(), $oFromIdentity->Name())); - } - else - { - $oMessage->SetFrom(\MailSo\Mime\Email::Parse($oAccount->Email())); - } - - $oFrom = $oMessage->GetFrom(); - $oMessage->RegenerateMessageId($oFrom ? $oFrom->GetDomain() : ''); - - if (!empty($sReplyTo)) - { - $oReplyTo = new \MailSo\Mime\EmailCollection($sReplyTo); - if ($oReplyTo && 0 < $oReplyTo->Count()) - { - $oMessage->SetReplyTo($oReplyTo); - } - } - - if ($bReadReceiptRequest) - { - $oMessage->SetReadReceipt($oAccount->Email()); - } - - if ($bMarkAsImportant) - { - $oMessage->SetPriority(\MailSo\Mime\Enumerations\MessagePriority::HIGH); - } - - $oMessage->SetSubject($sSubject); - - $oToEmails = new \MailSo\Mime\EmailCollection($sTo); - if ($oToEmails && $oToEmails->Count()) - { - $oMessage->SetTo($oToEmails); - } - - $oCcEmails = new \MailSo\Mime\EmailCollection($sCc); - if ($oCcEmails && $oCcEmails->Count()) - { - $oMessage->SetCc($oCcEmails); - } - - $oBccEmails = new \MailSo\Mime\EmailCollection($sBcc); - if ($oBccEmails && $oBccEmails->Count()) - { - $oMessage->SetBcc($oBccEmails); - } - - if ($bWithDraftInfo && \is_array($aDraftInfo) && !empty($aDraftInfo[0]) && !empty($aDraftInfo[1]) && !empty($aDraftInfo[2])) - { - $oMessage->SetDraftInfo($aDraftInfo[0], $aDraftInfo[1], $aDraftInfo[2]); - } - - if (0 < \strlen($sInReplyTo)) - { - $oMessage->SetInReplyTo($sInReplyTo); - } - - if (0 < \strlen($sReferences)) - { - $oMessage->SetReferences($sReferences); - } - - $aFoundedCids = array(); - $mFoundDataURL = array(); - $aFoundedContentLocationUrls = array(); - - $sTextToAdd = $bTextIsHtml ? - \MailSo\Base\HtmlUtils::BuildHtml($sText, $aFoundedCids, $mFoundDataURL, $aFoundedContentLocationUrls) : $sText; - - $this->Plugins()->RunHook($bTextIsHtml ? 'filter.message-html' : 'filter.message-plain', - array($oAccount, $oMessage, &$sTextToAdd)); - - if ($bTextIsHtml && 0 < \strlen($sTextToAdd)) - { - $sTextConverted = \MailSo\Base\HtmlUtils::ConvertHtmlToPlain($sTextToAdd); - $this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sTextConverted)); - $oMessage->AddText($sTextConverted, false); - } - - $oMessage->AddText($sTextToAdd, $bTextIsHtml); - - if (\is_array($aAttachments)) - { - foreach ($aAttachments as $sTempName => $aData) - { - $sFileName = (string) $aData[0]; - $bIsInline = (bool) $aData[1]; - $sCID = (string) $aData[2]; - $sContentLocation = isset($aData[3]) ? (string) $aData[3] : ''; - - $rResource = $this->FilesProvider()->GetFile($oAccount, $sTempName); - if (\is_resource($rResource)) - { - $iFileSize = $this->FilesProvider()->FileSize($oAccount, $sTempName); - - $oMessage->Attachments()->append( - new \MailSo\Mime\Attachment($rResource, $sFileName, $iFileSize, $bIsInline, - \in_array(trim(trim($sCID), '<>'), $aFoundedCids), - $sCID, array(), $sContentLocation - ) - ); - } - } - } - - if ($mFoundDataURL && \is_array($mFoundDataURL) && 0 < \count($mFoundDataURL)) - { - foreach ($mFoundDataURL as $sCidHash => $sDataUrlString) - { - $aMatch = array(); - $sCID = '<'.$sCidHash.'>'; - if (\preg_match('/^data:(image\/[a-zA-Z0-9]+);base64,(.+)$/i', $sDataUrlString, $aMatch) && - !empty($aMatch[1]) && !empty($aMatch[2])) - { - $sRaw = \MailSo\Base\Utils::Base64Decode($aMatch[2]); - $iFileSize = \strlen($sRaw); - if (0 < $iFileSize) - { - $sFileName = \preg_replace('/[^a-z0-9]+/i', '.', $aMatch[1]); - $rResource = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString($sRaw); - - $sRaw = ''; - unset($sRaw); - unset($aMatch); - - $oMessage->Attachments()->append( - new \MailSo\Mime\Attachment($rResource, $sFileName, $iFileSize, true, true, $sCID) - ); - } - } - } - } - - $this->Plugins()->RunHook('filter.build-message', array($oMessage)); - - return $oMessage; - } - - private function deleteMessageAttachmnets(Model\Account $oAccount) : void - { - $aAttachments = $this->GetActionParam('Attachments', null); - - if (\is_array($aAttachments)) - { - foreach (\array_keys($aAttachments) as $sTempName) - { - if ($this->FilesProvider()->FileExists($oAccount, $sTempName)) - { - $this->FilesProvider()->Clear($oAccount, $sTempName); - } - } - } - } - - private function buildReadReceiptMessage(Model\Account $oAccount) : \MailSo\Mime\Message - { - $sReadReceipt = $this->GetActionParam('ReadReceipt', ''); - $sSubject = $this->GetActionParam('Subject', ''); - $sText = $this->GetActionParam('Text', ''); - - $oIdentity = $this->GetAccountIdentity($oAccount); - - if (empty($sReadReceipt) || empty($sSubject) || empty($sText) || !$oIdentity) - { - throw new Exceptions\ClientException(Notifications::UnknownError); - } - - $oMessage = new \MailSo\Mime\Message(); - - if (!$this->Config()->Get('security', 'hide_x_mailer_header', true)) - { - $oMessage->SetXMailer('SnappyMail/'.APP_VERSION); - } - - $oMessage->SetFrom(new \MailSo\Mime\Email($oIdentity->Email(), $oIdentity->Name())); - - $oFrom = $oMessage->GetFrom(); - $oMessage->RegenerateMessageId($oFrom ? $oFrom->GetDomain() : ''); - - $sReplyTo = $oIdentity->ReplyTo(); - if (!empty($sReplyTo)) - { - $oReplyTo = new \MailSo\Mime\EmailCollection($sReplyTo); - if ($oReplyTo && $oReplyTo->Count()) - { - $oMessage->SetReplyTo($oReplyTo); - } - } - - $oMessage->SetSubject($sSubject); - - $oToEmails = new \MailSo\Mime\EmailCollection($sReadReceipt); - if ($oToEmails && $oToEmails->Count()) - { - $oMessage->SetTo($oToEmails); - } - - $this->Plugins()->RunHook('filter.read-receipt-message-plain', array($oAccount, $oMessage, &$sText)); - - $oMessage->AddText($sText, false); - - $this->Plugins()->RunHook('filter.build-read-receipt-message', array($oMessage, $oAccount)); - - return $oMessage; - } - - /** - * @throws \RainLoop\Exceptions\ClientException - * @throws \MailSo\Net\Exceptions\ConnectionException - */ - private function smtpSendMessage(Model\Account $oAccount, \MailSo\Mime\Message $oMessage, - /*resource*/ &$rMessageStream, int &$iMessageStreamSize, bool $bDsn = false, bool $bAddHiddenRcpt = true) - { - $oRcpt = $oMessage->GetRcpt(); - if ($oRcpt && 0 < $oRcpt->Count()) - { - $this->Plugins()->RunHook('filter.smtp-message-stream', - array($oAccount, &$rMessageStream, &$iMessageStreamSize)); - - $this->Plugins()->RunHook('filter.message-rcpt', array($oAccount, $oRcpt)); - - try - { - $oFrom = $oMessage->GetFrom(); - $sFrom = $oFrom instanceof \MailSo\Mime\Email ? $oFrom->GetEmail() : ''; - $sFrom = empty($sFrom) ? $oAccount->Email() : $sFrom; - - $this->Plugins()->RunHook('filter.smtp-from', array($oAccount, $oMessage, &$sFrom)); - - $aHiddenRcpt = array(); - if ($bAddHiddenRcpt) - { - $this->Plugins()->RunHook('filter.smtp-hidden-rcpt', array($oAccount, $oMessage, &$aHiddenRcpt)); - } - - $bUsePhpMail = $oAccount->Domain()->OutUsePhpMail(); - - $oSmtpClient = new \MailSo\Smtp\SmtpClient(); - $oSmtpClient->SetLogger($this->Logger()); - $oSmtpClient->SetTimeOuts(10, (int) Api::Config()->Get('labs', 'smtp_timeout', 60)); - - $bLoggined = $oAccount->OutConnectAndLoginHelper( - $this->Plugins(), $oSmtpClient, $this->Config(), null, $bUsePhpMail - ); - - if ($bUsePhpMail) - { - if (\MailSo\Base\Utils::FunctionExistsAndEnabled('mail')) - { - $aToCollection = $oMessage->GetTo(); - if ($aToCollection && $oFrom) - { - $sRawBody = \stream_get_contents($rMessageStream); - if (!empty($sRawBody)) - { - $sMailTo = \trim($aToCollection->ToString(true)); - $sMailSubject = \trim($oMessage->GetSubject()); - $sMailSubject = 0 === \strlen($sMailSubject) ? '' : \MailSo\Base\Utils::EncodeUnencodedValue( - \MailSo\Base\Enumerations\Encoding::BASE64_SHORT, $sMailSubject); - - $sMailHeaders = $sMailBody = ''; - list($sMailHeaders, $sMailBody) = \explode("\r\n\r\n", $sRawBody, 2); - unset($sRawBody); - - if ($this->Config()->Get('labs', 'mail_func_clear_headers', true)) - { - $sMailHeaders = \MailSo\Base\Utils::RemoveHeaderFromHeaders($sMailHeaders, array( - \MailSo\Mime\Enumerations\Header::TO_, - \MailSo\Mime\Enumerations\Header::SUBJECT - )); - } - - if ($this->Config()->Get('debug', 'enable', false)) - { - $this->Logger()->WriteDump(array( - $sMailTo, $sMailSubject, $sMailBody, $sMailHeaders - )); - } - - $bR = $this->Config()->Get('labs', 'mail_func_additional_parameters', false) ? - \mail($sMailTo, $sMailSubject, $sMailBody, $sMailHeaders, '-f'.$oFrom->GetEmail()) : - \mail($sMailTo, $sMailSubject, $sMailBody, $sMailHeaders); - - if (!$bR) - { - throw new Exceptions\ClientException(Notifications::CantSendMessage); - } - } - } - } - else - { - throw new Exceptions\ClientException(Notifications::CantSendMessage); - } - } - else if ($oSmtpClient->IsConnected()) - { - if (!empty($sFrom)) - { - $oSmtpClient->MailFrom($sFrom, '', $bDsn); - } - - foreach ($oRcpt as /* @var $oEmail \MailSo\Mime\Email */ $oEmail) - { - $oSmtpClient->Rcpt($oEmail->GetEmail(), $bDsn); - } - - if ($bAddHiddenRcpt && \is_array($aHiddenRcpt) && 0 < \count($aHiddenRcpt)) - { - foreach ($aHiddenRcpt as $sEmail) - { - if (\preg_match('/^[^@\s]+@[^@\s]+$/', $sEmail)) - { - $oSmtpClient->Rcpt($sEmail); - } - } - } - - $oSmtpClient->DataWithStream($rMessageStream); - - if ($bLoggined) - { - $oSmtpClient->Logout(); - } - - $oSmtpClient->Disconnect(); - } - } - catch (\MailSo\Net\Exceptions\ConnectionException $oException) - { - if ($this->Config()->Get('labs', 'smtp_show_server_errors')) - { - throw new Exceptions\ClientException(Notifications::ClientViewError, $oException); - } - else - { - throw new Exceptions\ClientException(Notifications::ConnectionError, $oException); - } - } - catch (\MailSo\Smtp\Exceptions\LoginException $oException) - { - throw new Exceptions\ClientException(Notifications::AuthError, $oException); - } - catch (\Throwable $oException) - { - if ($this->Config()->Get('labs', 'smtp_show_server_errors')) - { - throw new Exceptions\ClientException(Notifications::ClientViewError, $oException); - } - else - { - throw $oException; - } - } - } - else - { - throw new Exceptions\ClientException(Notifications::InvalidRecipients); - } - } - - private function getContactsSyncData(Model\Account $oAccount) : ?array - { - $mResult = null; - - $sData = $this->StorageProvider()->Get($oAccount, - Providers\Storage\Enumerations\StorageType::CONFIG, - 'contacts_sync' - ); - - if (!empty($sData)) - { - $aData = Utils::DecodeKeyValues($sData); - if ($aData) - { - $mResult = array( - 'Enable' => isset($aData['Enable']) ? !!$aData['Enable'] : false, - 'Url' => isset($aData['Url']) ? \trim($aData['Url']) : '', - 'User' => isset($aData['User']) ? \trim($aData['User']) : '', - 'Password' => isset($aData['Password']) ? $aData['Password'] : '' - ); - } - } - - return $mResult; - } - - private function getTwoFactorInfo(Model\Account $oAccount, bool $bRemoveSecret = false) : array - { - $sEmail = $oAccount->ParentEmailHelper(); - - $mData = null; - - $aResult = array( - 'User' => '', - 'IsSet' => false, - 'Enable' => false, - 'Secret' => '', - 'UrlTitle' => '', - 'BackupCodes' => '' - ); - - if (!empty($sEmail)) - { - $aResult['User'] = $sEmail; - - $sData = $this->StorageProvider()->Get($oAccount, - Providers\Storage\Enumerations\StorageType::CONFIG, - 'two_factor' - ); - - if ($sData) - { - $mData = Utils::DecodeKeyValues($sData); - } - } - - if (!empty($aResult['User']) && - !empty($mData['User']) && !empty($mData['Secret']) && - !empty($mData['BackupCodes']) && $sEmail === $mData['User']) - { - $aResult['IsSet'] = true; - $aResult['Enable'] = isset($mData['Enable']) ? !!$mData['Enable'] : false; - $aResult['Secret'] = $mData['Secret']; - $aResult['BackupCodes'] = $mData['BackupCodes']; - $aResult['UrlTitle'] = $this->Config()->Get('webmail', 'title', ''); - } - - if ($bRemoveSecret) - { - if (isset($aResult['Secret'])) - { - unset($aResult['Secret']); - } - - if (isset($aResult['UrlTitle'])) - { - unset($aResult['UrlTitle']); - } - - if (isset($aResult['BackupCodes'])) - { - unset($aResult['BackupCodes']); - } - } - - return $aResult; - } - - private function removeBackupCodeFromTwoFactorInfo(Model\Account $oAccount, string $sCode) : bool - { - if (!$oAccount || empty($sCode)) - { - return false; - } - - $sData = $this->StorageProvider()->Get($oAccount, - Providers\Storage\Enumerations\StorageType::CONFIG, - 'two_factor' - ); - - if ($sData) - { - $mData = Utils::DecodeKeyValues($sData); - - if (!empty($mData['BackupCodes'])) - { - $sBackupCodes = \preg_replace('/[^\d]+/', ' ', ' '.$mData['BackupCodes'].' '); - $sBackupCodes = \str_replace(' '.$sCode.' ', '', $sBackupCodes); - - $mData['BackupCodes'] = \trim(\preg_replace('/[^\d]+/', ' ', $sBackupCodes)); - - return $this->StorageProvider()->Put($oAccount, - Providers\Storage\Enumerations\StorageType::CONFIG, - 'two_factor', - Utils::EncodeKeyValues($mData) - ); - } - } - - return false; - } - - private function messageSetFlag(string $sActionFunction, string $sResponseFunction) : array - { - $this->initMailClientConnection(); - - $sFolder = $this->GetActionParam('Folder', ''); - $bSetAction = '1' === (string) $this->GetActionParam('SetAction', '0'); - $aUids = \explode(',', (string) $this->GetActionParam('Uids', '')); - $aFilteredUids = \array_filter($aUids, function (&$sUid) { - $sUid = (int) \trim($sUid); - return 0 < $sUid; - }); - - try - { - $this->MailClient()->{$sActionFunction}($sFolder, $aFilteredUids, true, $bSetAction, true); - } - catch (\Throwable $oException) - { - throw new Exceptions\ClientException(Notifications::MailServerError, $oException); - } - - return $this->TrueResponse($sResponseFunction); - } - public function MainClearFileName(string $sFileName, string $sContentType, string $sMimeIndex, int $iMaxLength = 250) : string { $sFileName = 0 === \strlen($sFileName) ? \preg_replace('/[^a-zA-Z0-9]/', '.', (empty($sMimeIndex) ? '' : $sMimeIndex.'.').$sContentType) : $sFileName; @@ -3161,71 +2274,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut ) : false); } - public function UploadContacts() : array - { - $oAccount = $this->getAccountFromToken(); - - $mResponse = false; - - $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']); - if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) - { - $iError = Enumerations\UploadError::ON_SAVING; - } - else - { - \ini_set('auto_detect_line_endings', true); - $mData = $this->FilesProvider()->GetFile($oAccount, $sSavedName); - if ($mData) - { - $sFileStart = \fread($mData, 20); - \rewind($mData); - - if (false !== $sFileStart) - { - $sFileStart = \trim($sFileStart); - if (false !== \strpos($sFileStart, 'BEGIN:VCARD')) - { - $mResponse = $this->importContactsFromVcfFile($oAccount, $mData); - } - else if (false !== \strpos($sFileStart, ',') || false !== \strpos($sFileStart, ';')) - { - $mResponse = $this->importContactsFromCsvFile($oAccount, $mData, $sFileStart); - } - } - } - - if (\is_resource($mData)) - { - \fclose($mData); - } - - unset($mData); - $this->FilesProvider()->Clear($oAccount, $sSavedName); - - \ini_set('auto_detect_line_endings', false); - } - } - - if (UPLOAD_ERR_OK !== $iError) - { - $iClientError = Enumerations\UploadClientError::NORMAL; - $sError = $this->getUploadErrorMessageByCode($iError, $iClientError); - - if (!empty($sError)) - { - return $this->FalseResponse(__FUNCTION__, $iClientError, $sError); - } - } - - return $this->DefaultResponse(__FUNCTION__, $mResponse); - } - private function importContactsFromCsvFile(Model\Account $oAccount, /*resource*/ $rFile, string $sFileStart) : int { $iCount = 0; @@ -3304,30 +2352,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut return $iCount; } - /** - * @throws \MailSo\Base\Exceptions\Exception - */ - public function RawViewAsPlain() : bool - { - $this->initMailClientConnection(); - - $sRawKey = (string) $this->GetActionParam('RawKey', ''); - $aValues = $this->getDecodedRawKeyValue($sRawKey); - - $sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : ''; - $iUid = (int) (isset($aValues['Uid']) ? $aValues['Uid'] : 0); - $sMimeIndex = (string) (isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : ''); - - \header('Content-Type: text/plain', true); - - return $this->MailClient()->MessageMimeStream(function ($rResource) { - if (\is_resource($rResource)) - { - \MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource); - } - }, $sFolder, $iUid, true, $sMimeIndex); - } - /** * @throws \MailSo\Base\Exceptions\Exception */ @@ -3548,457 +2572,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut } } - private function getMimeFileByHash(Model\Account $oAccount, string $sHash) : array - { - $aValues = $this->getDecodedRawKeyValue($sHash); - - $sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : ''; - $iUid = (int) isset($aValues['Uid']) ? $aValues['Uid'] : 0; - $sMimeIndex = (string) isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : ''; - - $sContentTypeIn = (string) isset($aValues['MimeType']) ? $aValues['MimeType'] : ''; - $sFileNameIn = (string) isset($aValues['FileName']) ? $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::Md5Rand($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, true, $sMimeIndex); - - $aValues['FileHash'] = ''; - if ($mResult) - { - $aValues['FileHash'] = $sResultHash; - } - - return $aValues; - } - - private function rotateImageByOrientation(\Imagine\Image\AbstractImage $oImage, int $iOrientation) : void - { - if (0 < $iOrientation) - { - switch ($iOrientation) - { - default: - case 1: - break; - - case 2: // flip horizontal - $oImage->flipHorizontally(); - break; - - case 3: // rotate 180 - $oImage->rotate(180); - break; - - case 4: // flip vertical - $oImage->flipVertically(); - break; - - case 5: // vertical flip + 90 rotate - $oImage->flipVertically(); - $oImage->rotate(90); - break; - - case 6: // rotate 90 - $oImage->rotate(90); - break; - - case 7: // horizontal flip + 90 rotate - $oImage->flipHorizontally(); - $oImage->rotate(90); - break; - - case 8: // rotate 270 - $oImage->rotate(270); - break; - } - } - } - - public function correctImageOrientation(\Imagine\Image\AbstractImage $oImage, bool $bDetectImageOrientation = true, int $iThumbnailBoxSize = 0) : \Imagine\Image\AbstractImage - { - $iOrientation = 1; - if ($bDetectImageOrientation && \MailSo\Base\Utils::FunctionExistsAndEnabled('exif_read_data') && - \MailSo\Base\Utils::FunctionExistsAndEnabled('gd_info')) - { - $oMetadata = $oImage->metadata(new \Imagine\Image\Metadata\ExifMetadataReader()); - $iOrientation = isset($oMetadata['ifd0.Orientation']) && - is_numeric($oMetadata['ifd0.Orientation']) ? (int) $oMetadata['ifd0.Orientation'] : 1; - } - - if (0 < $iThumbnailBoxSize) - { - $oImage = $oImage->thumbnail( - new \Imagine\Image\Box($iThumbnailBoxSize, $iThumbnailBoxSize), - \Imagine\Image\ImageInterface::THUMBNAIL_OUTBOUND); - - $this->rotateImageByOrientation($oImage, $iOrientation); - } - else - { - $this->rotateImageByOrientation($oImage, $iOrientation); - } - - return $oImage; - } - - private function rawSmart(bool $bDownload, bool $bThumbnail = false) : bool - { - $sRawKey = (string) $this->GetActionParam('RawKey', ''); - $aValues = $this->getDecodedRawKeyValue($sRawKey); - - $sRange = $this->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)) - { - $sRangeStart = $aMatch[1]; - $sRangeEnd = $aMatch[2]; - - $bIsRangeRequest = true; - } - - $sFolder = isset($aValues['Folder']) ? $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'] : ''; - $sFileHashIn = isset($aValues['FileHash']) ? (string) $aValues['FileHash'] : ''; - - $bDetectImageOrientation = !!$this->Config()->Get('labs', 'detect_image_exif_orientation', true); - - if (!empty($sFileHashIn)) - { - $this->verifyCacheByKey($sRawKey); - - $oAccount = $this->getAccountFromToken(); - - $sContentTypeOut = empty($sContentTypeIn) ? - \MailSo\Base\Utils::MimeContentType($sFileNameIn) : $sContentTypeIn; - - $sFileNameOut = $this->MainClearFileName($sFileNameIn, $sContentTypeIn, $sMimeIndex); - - $rResource = $this->FilesProvider()->GetFile($oAccount, $sFileHashIn); - if (\is_resource($rResource)) - { - \header('Content-Type: '.$sContentTypeOut); - \header('Content-Disposition: attachment; '. - \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)), true); - - \header('Accept-Ranges: none', true); - \header('Content-Transfer-Encoding: binary'); - - \MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource); - return true; - } - - return false; - } - else - { - if (!empty($sFolder) && 0 < $iUid) - { - $this->verifyCacheByKey($sRawKey); - } - } - - $oAccount = $this->initMailClientConnection(); - - $self = $this; - return $this->MailClient()->MessageMimeStream( - function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use ( - $self, $oAccount, $sRawKey, $sContentTypeIn, $sFileNameIn, $bDownload, $bThumbnail, $bDetectImageOrientation, - $bIsRangeRequest, $sRangeStart, $sRangeEnd - ) { - if ($oAccount && \is_resource($rResource)) - { - \MailSo\Base\Utils::ResetTimeLimit(); - - $sContentTypeOut = $sContentTypeIn; - if (empty($sContentTypeOut)) - { - $sContentTypeOut = $sContentType; - if (empty($sContentTypeOut)) - { - $sContentTypeOut = (empty($sFileName)) ? 'text/plain' : \MailSo\Base\Utils::MimeContentType($sFileName); - } - } - - $sFileNameOut = $sFileNameIn; - if (empty($sFileNameOut)) - { - $sFileNameOut = $sFileName; - } - - $sFileNameOut = $self->MainClearFileName($sFileNameOut, $sContentTypeOut, $sMimeIndex); - - $self->cacheByKey($sRawKey); - - $sLoadedData = null; - if (!$bDownload) - { - if ($bThumbnail) - { - try - { - $oImagine = new \Imagine\Gd\Imagine(); - - $oImage = $oImagine->load(\stream_get_contents($rResource)); - - $oImage = $self->correctImageOrientation($oImage, $bDetectImageOrientation, 60); - - \header('Content-Disposition: inline; '. - \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut.'_thumb60x60.png')), true); - - $oImage->show('png'); - } - catch (\Throwable $oException) - { - $self->Logger()->WriteExceptionShort($oException); - } - } - else if ($bDetectImageOrientation && - \in_array($sContentTypeOut, array('image/png', 'image/jpeg', 'image/jpg')) && - \MailSo\Base\Utils::FunctionExistsAndEnabled('gd_info')) - { - try - { - $oImagine = new \Imagine\Gd\Imagine(); - - $sLoadedData = \stream_get_contents($rResource); - - $oImage = $oImagine->load($sLoadedData); - - $oImage = $self->correctImageOrientation($oImage, $bDetectImageOrientation); - - \header('Content-Disposition: inline; '. - \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)), true); - - $oImage->show($sContentTypeOut === 'image/png' ? 'png' : 'jpg'); - } - catch (\Throwable $oException) - { - $self->Logger()->WriteExceptionShort($oException); - } - } - else - { - $sLoadedData = \stream_get_contents($rResource); - } - } - - if ($bDownload || $sLoadedData) - { - if (!headers_sent()) { - \header('Content-Type: '.$sContentTypeOut); - \header('Content-Disposition: '.($bDownload ? 'attachment' : 'inline').'; '. - \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)), true); - - \header('Accept-Ranges: bytes'); - \header('Content-Transfer-Encoding: binary'); - } - - if ($bIsRangeRequest && !$sLoadedData) - { - $sLoadedData = \stream_get_contents($rResource); - } - - \MailSo\Base\Utils::ResetTimeLimit(); - - if ($sLoadedData) - { - if ($bIsRangeRequest && (0 < \strlen($sRangeStart) || 0 < \strlen($sRangeEnd))) - { - $iFullContentLength = \strlen($sLoadedData); - - $self->Http()->StatusHeader(206); - - $iRangeStart = (int) $sRangeStart; - $iRangeEnd = (int) $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; - } - } - - $iContentLength = \strlen($sLoadedData); - - if (0 < $iContentLength) - { - \header('Content-Length: '.$iContentLength, true); - \header('Content-Range: bytes '.$sRangeStart.'-'.(0 < $iRangeEnd ? $iRangeEnd : $iFullContentLength - 1).'/'.$iFullContentLength); - } - - echo $sLoadedData; - } - else - { - echo $sLoadedData; - } - - unset($sLoadedData); - } - else - { - \MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource); - } - } - } - }, $sFolder, $iUid, true, $sMimeIndex); - } - - public function RawDownload() : bool - { - return $this->rawSmart(true); - } - - public function RawView() : bool - { - return $this->rawSmart(false); - } - - public function RawViewThumbnail() : bool - { - return $this->rawSmart(false, true); - } - - public function RawUserBackground() : bool - { - $sRawKey = (string) $this->GetActionParam('RawKey', ''); - $this->verifyCacheByKey($sRawKey); - - $oAccount = $this->getAccountFromToken(); - - $sData = $this->StorageProvider()->Get($oAccount, - 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'))) - { - $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, - Providers\Storage\Enumerations\StorageType::NOBODY, - 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); - - return true; - } - } - - return false; - } - - public function RawContactsVcf() : bool - { - $oAccount = $this->getAccountFromToken(); - - \header('Content-Type: text/x-vcard; charset=UTF-8'); - \header('Content-Disposition: attachment; filename="contacts.vcf"', true); - \header('Accept-Ranges: none', true); - \header('Content-Transfer-Encoding: binary'); - - $this->oHttp->ServerNoCache(); - - return $this->AddressBookProvider($oAccount)->IsActive() ? - $this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'vcf') : false; - } - - public function RawContactsCsv() : bool - { - $oAccount = $this->getAccountFromToken(); - - \header('Content-Type: text/csv; charset=UTF-8'); - \header('Content-Disposition: attachment; filename="contacts.csv"', true); - \header('Accept-Ranges: none', true); - \header('Content-Transfer-Encoding: binary'); - - $this->oHttp->ServerNoCache(); - - return $this->AddressBookProvider($oAccount)->IsActive() ? - $this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'csv') : false; - } - private function initMailClientConnection() : ?Model\Account { $oAccount = null; @@ -4031,22 +2604,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut return empty($sRawKey) ? array() : Utils::DecodeKeyValuesQ($sRawKey); } - private function getDecodedClientRawKeyValue(string $sRawKey, ?int $iLenCache = null) : ?array - { - if (!empty($sRawKey)) - { - $sRawKey = \MailSo\Base\Utils::UrlSafeBase64Decode($sRawKey); - $aValues = explode("\x0", $sRawKey); - - if (null === $iLenCache || $iLenCache === count($aValues)) - { - return $aValues; - } - } - - return null; - } - public function StaticCache() : string { static $sCache = null; @@ -4302,19 +2859,6 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut return isset($this->aCurrentActionParams[$sKey]); } - public function HasOneOfActionParams(array $aKeys) : bool - { - foreach ($aKeys as $sKey) - { - if ($this->HasActionParam($sKey)) - { - return true; - } - } - - return false; - } - public function Location(string $sUrl) : void { $this->Logger()->Write('Location: '.$sUrl); @@ -4389,66 +2933,4 @@ NewThemeLink IncludeCss TemplatesLink LangLink IncludeBackground PluginsLink Aut return Utils::WebStaticPath().$sPath; } - /** - * @return MailSo\Cache\CacheClient|null - */ - private function cacherForUids() - { - $oAccount = $this->getAccountFromToken(false); - return ($this->Config()->Get('cache', 'enable', true) && - $this->Config()->Get('cache', 'server_uids', false)) ? $this->Cacher($oAccount) : null; - } - - /** - * @return MailSo\Cache\CacheClient|null - */ - private function cacherForThreads() - { - $oAccount = $this->getAccountFromToken(false); - return !!$this->Config()->Get('labs', 'use_imap_thread', false) ? $this->Cacher($oAccount) : null; - } - - private function explodeSubject(string $sSubject) : array - { - $aResult = array('', ''); - if (0 < \strlen($sSubject)) - { - $bDrop = false; - $aPrefix = array(); - $aSuffix = array(); - - $aParts = \explode(':', $sSubject); - foreach ($aParts as $sPart) - { - if (!$bDrop && - (\preg_match('/^(RE|FWD)$/i', \trim($sPart)) || \preg_match('/^(RE|FWD)[\[\(][\d]+[\]\)]$/i', \trim($sPart)))) - { - $aPrefix[] = $sPart; - } - else - { - $aSuffix[] = $sPart; - $bDrop = true; - } - } - - if (0 < \count($aPrefix)) - { - $aResult[0] = \rtrim(\trim(\implode(':', $aPrefix)), ':').': '; - } - - if (0 < \count($aSuffix)) - { - $aResult[1] = \trim(\implode(':', $aSuffix)); - } - - if (0 === \strlen($aResult[1])) - { - $aResult = array('', $sSubject); - } - } - - return $aResult; - } - } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php index 2588f1a2c..a66f8c1fc 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php @@ -214,4 +214,26 @@ trait Accounts )); } + private function SetIdentities(\RainLoop\Model\Account $oAccount, array $aIdentities = array()) : bool + { + $bAllowIdentities = $this->GetCapa(false, false, Capa::IDENTITIES, $oAccount); + + $aResult = array(); + foreach ($aIdentities as $oItem) + { + if (!$bAllowIdentities && $oItem && !$oItem->IsAccountIdentities()) + { + continue; + } + + $aResult[] = $oItem->ToSimpleJSON(); + } + + return $this->StorageProvider(true)->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'identities', + \json_encode($aResult) + ); + } + } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php index 55ed91256..9d2dcbf02 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php @@ -1005,4 +1005,16 @@ trait Admin return $this->DefaultResponse(__FUNCTION__, true); } + private function HasOneOfActionParams(array $aKeys) : bool + { + foreach ($aKeys as $sKey) + { + if ($this->HasActionParam($sKey)) + { + return true; + } + } + + return false; + } } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php index 44aca9f4d..182b7fa06 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Contacts.php @@ -173,4 +173,90 @@ trait Contacts )); } + public function UploadContacts() : 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)) + { + $sSavedName = 'upload-post-'.\md5($aFile['name'].$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); + $mData = $this->FilesProvider()->GetFile($oAccount, $sSavedName); + if ($mData) + { + $sFileStart = \fread($mData, 20); + \rewind($mData); + + if (false !== $sFileStart) + { + $sFileStart = \trim($sFileStart); + if (false !== \strpos($sFileStart, 'BEGIN:VCARD')) + { + $mResponse = $this->importContactsFromVcfFile($oAccount, $mData); + } + else if (false !== \strpos($sFileStart, ',') || false !== \strpos($sFileStart, ';')) + { + $mResponse = $this->importContactsFromCsvFile($oAccount, $mData, $sFileStart); + } + } + } + + if (\is_resource($mData)) + { + \fclose($mData); + } + + unset($mData); + $this->FilesProvider()->Clear($oAccount, $sSavedName); + + \ini_set('auto_detect_line_endings', false); + } + } + + if (UPLOAD_ERR_OK !== $iError) + { + $iClientError = \RainLoop\Enumerations\UploadClientError::NORMAL; + $sError = $this->getUploadErrorMessageByCode($iError, $iClientError); + + if (!empty($sError)) + { + return $this->FalseResponse(__FUNCTION__, $iClientError, $sError); + } + } + + return $this->DefaultResponse(__FUNCTION__, $mResponse); + } + + protected function getContactsSyncData(\RainLoop\Model\Account $oAccount) : ?array + { + $mResult = null; + $sData = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'contacts_sync' + ); + if (!empty($sData)) { + $aData = \RainLoop\Utils::DecodeKeyValues($sData); + if ($aData) { + $mResult = array( + 'Enable' => isset($aData['Enable']) ? !!$aData['Enable'] : false, + 'Url' => isset($aData['Url']) ? \trim($aData['Url']) : '', + 'User' => isset($aData['User']) ? \trim($aData['User']) : '', + 'Password' => isset($aData['Password']) ? $aData['Password'] : '' + ); + } + } + return $mResult; + } + } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Filters.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Filters.php index 54736e307..1221773a8 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Filters.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Filters.php @@ -6,6 +6,11 @@ use \RainLoop\Enumerations\Capa; trait Filters { + /** + * @var \RainLoop\Providers\Filters + */ + private $oFiltersProvider; + /** * @throws \MailSo\Base\Exceptions\Exception */ @@ -71,4 +76,11 @@ trait Filters $aFilters, $sRaw, $bRawIsActive)); } + protected function FiltersProvider() : \RainLoop\Providers\Filters + { + if (!$this->oFiltersProvider) { + $this->oFiltersProvider = new \RainLoop\Providers\Filters($this->fabrica('filters')); + } + return $this->oFiltersProvider; + } } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php index 9e06caa7f..343f715b7 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Folders.php @@ -448,4 +448,161 @@ trait Folders $this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal)); } + private function recFoldersNames(\MailSo\Mail\FolderCollection $oFolders) : array + { + $aResult = array(); + if ($oFolders) + { + foreach ($oFolders as $oFolder) + { + $aResult[] = $oFolder->FullNameRaw()."|". + implode("|", $oFolder->Flags()).($oFolder->IsSubscribed() ? '1' : '0'); + + $oSub = $oFolder->SubFolders(); + if ($oSub && 0 < $oSub->Count()) + { + $aResult = \array_merge($aResult, $this->recFoldersNames($oSub)); + } + } + } + + return $aResult; + } + + private function recFoldersTypes(\RainLoop\Model\Account $oAccount, \MailSo\Mail\FolderCollection $oFolders, array &$aResult, bool $bListFolderTypes = true) : void + { + if ($oFolders && $oFolders->Count()) + { + if ($bListFolderTypes) + { + foreach ($oFolders as $oFolder) + { + $iFolderListType = $oFolder->GetFolderListType(); + if (!isset($aResult[$iFolderListType]) && \in_array($iFolderListType, array( + FolderType::INBOX, + FolderType::SENT, + FolderType::DRAFTS, + FolderType::JUNK, + FolderType::TRASH, + FolderType::ALL + ))) + { + $aResult[$iFolderListType] = $oFolder->FullNameRaw(); + } + } + + foreach ($oFolders as $oFolder) + { + $oSub = $oFolder->SubFolders(); + if ($oSub && 0 < $oSub->Count()) + { + $this->recFoldersTypes($oAccount, $oSub, $aResult, true); + } + } + } + + $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]) && \in_array($iFolderType, array( + FolderType::INBOX, + FolderType::SENT, + FolderType::DRAFTS, + FolderType::JUNK, + FolderType::TRASH, + FolderType::ALL + ))) + { + $aResult[$iFolderType] = $oFolder->FullNameRaw(); + } + } + } + + foreach ($oFolders as $oFolder) + { + $oSub = $oFolder->SubFolders(); + if ($oSub && 0 < $oSub->Count()) + { + $this->recFoldersTypes($oAccount, $oSub, $aResult, false); + } + } + } + } + + /** + * @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::ALL, + 'Archives' => FolderType::ALL, + + '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; + } } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php index 86a66bf21..f723452ff 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php @@ -4,6 +4,7 @@ namespace RainLoop\Actions; use \RainLoop\Enumerations\Capa; use \RainLoop\Exceptions\ClientException; +use \RainLoop\Model\Account; use \RainLoop\Notifications; trait Messages @@ -297,7 +298,7 @@ trait Messages } } } - catch (Exceptions\ClientException $oException) + catch (ClientException $oException) { throw $oException; } @@ -398,7 +399,7 @@ trait Messages } } } - catch (Exceptions\ClientException $oException) + catch (ClientException $oException) { throw $oException; } @@ -698,4 +699,456 @@ trait Messages return $this->DefaultResponse(__FUNCTION__, $mResult); } + /** + * @throws \RainLoop\Exceptions\ClientException + * @throws \MailSo\Net\Exceptions\ConnectionException + */ + private function smtpSendMessage(Account $oAccount, \MailSo\Mime\Message $oMessage, + /*resource*/ &$rMessageStream, int &$iMessageStreamSize, bool $bDsn = false, bool $bAddHiddenRcpt = true) + { + $oRcpt = $oMessage->GetRcpt(); + if ($oRcpt && 0 < $oRcpt->Count()) + { + $this->Plugins()->RunHook('filter.smtp-message-stream', + array($oAccount, &$rMessageStream, &$iMessageStreamSize)); + + $this->Plugins()->RunHook('filter.message-rcpt', array($oAccount, $oRcpt)); + + try + { + $oFrom = $oMessage->GetFrom(); + $sFrom = $oFrom instanceof \MailSo\Mime\Email ? $oFrom->GetEmail() : ''; + $sFrom = empty($sFrom) ? $oAccount->Email() : $sFrom; + + $this->Plugins()->RunHook('filter.smtp-from', array($oAccount, $oMessage, &$sFrom)); + + $aHiddenRcpt = array(); + if ($bAddHiddenRcpt) + { + $this->Plugins()->RunHook('filter.smtp-hidden-rcpt', array($oAccount, $oMessage, &$aHiddenRcpt)); + } + + $bUsePhpMail = $oAccount->Domain()->OutUsePhpMail(); + + $oSmtpClient = new \MailSo\Smtp\SmtpClient(); + $oSmtpClient->SetLogger($this->Logger()); + $oSmtpClient->SetTimeOuts(10, (int) \RainLoop\Api::Config()->Get('labs', 'smtp_timeout', 60)); + + $bLoggined = $oAccount->OutConnectAndLoginHelper( + $this->Plugins(), $oSmtpClient, $this->Config(), null, $bUsePhpMail + ); + + if ($bUsePhpMail) + { + if (\MailSo\Base\Utils::FunctionExistsAndEnabled('mail')) + { + $aToCollection = $oMessage->GetTo(); + if ($aToCollection && $oFrom) + { + $sRawBody = \stream_get_contents($rMessageStream); + if (!empty($sRawBody)) + { + $sMailTo = \trim($aToCollection->ToString(true)); + $sMailSubject = \trim($oMessage->GetSubject()); + $sMailSubject = 0 === \strlen($sMailSubject) ? '' : \MailSo\Base\Utils::EncodeUnencodedValue( + \MailSo\Base\Enumerations\Encoding::BASE64_SHORT, $sMailSubject); + + $sMailHeaders = $sMailBody = ''; + list($sMailHeaders, $sMailBody) = \explode("\r\n\r\n", $sRawBody, 2); + unset($sRawBody); + + if ($this->Config()->Get('labs', 'mail_func_clear_headers', true)) + { + $sMailHeaders = \MailSo\Base\Utils::RemoveHeaderFromHeaders($sMailHeaders, array( + \MailSo\Mime\Enumerations\Header::TO_, + \MailSo\Mime\Enumerations\Header::SUBJECT + )); + } + + if ($this->Config()->Get('debug', 'enable', false)) + { + $this->Logger()->WriteDump(array( + $sMailTo, $sMailSubject, $sMailBody, $sMailHeaders + )); + } + + $bR = $this->Config()->Get('labs', 'mail_func_additional_parameters', false) ? + \mail($sMailTo, $sMailSubject, $sMailBody, $sMailHeaders, '-f'.$oFrom->GetEmail()) : + \mail($sMailTo, $sMailSubject, $sMailBody, $sMailHeaders); + + if (!$bR) + { + throw new ClientException(Notifications::CantSendMessage); + } + } + } + } + else + { + throw new ClientException(Notifications::CantSendMessage); + } + } + else if ($oSmtpClient->IsConnected()) + { + if (!empty($sFrom)) + { + $oSmtpClient->MailFrom($sFrom, '', $bDsn); + } + + foreach ($oRcpt as /* @var $oEmail \MailSo\Mime\Email */ $oEmail) + { + $oSmtpClient->Rcpt($oEmail->GetEmail(), $bDsn); + } + + if ($bAddHiddenRcpt && \is_array($aHiddenRcpt) && 0 < \count($aHiddenRcpt)) + { + foreach ($aHiddenRcpt as $sEmail) + { + if (\preg_match('/^[^@\s]+@[^@\s]+$/', $sEmail)) + { + $oSmtpClient->Rcpt($sEmail); + } + } + } + + $oSmtpClient->DataWithStream($rMessageStream); + + if ($bLoggined) + { + $oSmtpClient->Logout(); + } + + $oSmtpClient->Disconnect(); + } + } + catch (\MailSo\Net\Exceptions\ConnectionException $oException) + { + if ($this->Config()->Get('labs', 'smtp_show_server_errors')) + { + throw new ClientException(Notifications::ClientViewError, $oException); + } + else + { + throw new ClientException(Notifications::ConnectionError, $oException); + } + } + catch (\MailSo\Smtp\Exceptions\LoginException $oException) + { + throw new ClientException(Notifications::AuthError, $oException); + } + catch (\Throwable $oException) + { + if ($this->Config()->Get('labs', 'smtp_show_server_errors')) + { + throw new ClientException(Notifications::ClientViewError, $oException); + } + else + { + throw $oException; + } + } + } + else + { + throw new ClientException(Notifications::InvalidRecipients); + } + } + + private function messageSetFlag(string $sActionFunction, string $sResponseFunction) : array + { + $this->initMailClientConnection(); + + $sFolder = $this->GetActionParam('Folder', ''); + $bSetAction = '1' === (string) $this->GetActionParam('SetAction', '0'); + $aUids = \explode(',', (string) $this->GetActionParam('Uids', '')); + $aFilteredUids = \array_filter($aUids, function (&$sUid) { + $sUid = (int) \trim($sUid); + return 0 < $sUid; + }); + + try + { + $this->MailClient()->{$sActionFunction}($sFolder, $aFilteredUids, true, $bSetAction, true); + } + catch (\Throwable $oException) + { + throw new ClientException(Notifications::MailServerError, $oException); + } + + return $this->TrueResponse($sResponseFunction); + } + + private function getDecodedClientRawKeyValue(string $sRawKey, ?int $iLenCache = null) : ?array + { + if (!empty($sRawKey)) + { + $sRawKey = \MailSo\Base\Utils::UrlSafeBase64Decode($sRawKey); + $aValues = explode("\x0", $sRawKey); + + if (null === $iLenCache || $iLenCache === count($aValues)) + { + return $aValues; + } + } + + return null; + } + + private function deleteMessageAttachmnets(Account $oAccount) : void + { + $aAttachments = $this->GetActionParam('Attachments', null); + + if (\is_array($aAttachments)) + { + foreach (\array_keys($aAttachments) as $sTempName) + { + if ($this->FilesProvider()->FileExists($oAccount, $sTempName)) + { + $this->FilesProvider()->Clear($oAccount, $sTempName); + } + } + } + } + + /** + * @return MailSo\Cache\CacheClient|null + */ + private function cacherForUids() + { + $oAccount = $this->getAccountFromToken(false); + return ($this->Config()->Get('cache', 'enable', true) && + $this->Config()->Get('cache', 'server_uids', false)) ? $this->Cacher($oAccount) : null; + } + + /** + * @return MailSo\Cache\CacheClient|null + */ + private function cacherForThreads() + { + $oAccount = $this->getAccountFromToken(false); + return !!$this->Config()->Get('labs', 'use_imap_thread', false) ? $this->Cacher($oAccount) : null; + } + + private function buildReadReceiptMessage(Account $oAccount) : \MailSo\Mime\Message + { + $sReadReceipt = $this->GetActionParam('ReadReceipt', ''); + $sSubject = $this->GetActionParam('Subject', ''); + $sText = $this->GetActionParam('Text', ''); + + $oIdentity = $this->GetIdentityByID($oAccount, '', true); + + if (empty($sReadReceipt) || empty($sSubject) || empty($sText) || !$oIdentity) + { + throw new ClientException(Notifications::UnknownError); + } + + $oMessage = new \MailSo\Mime\Message(); + + if (!$this->Config()->Get('security', 'hide_x_mailer_header', true)) + { + $oMessage->SetXMailer('SnappyMail/'.APP_VERSION); + } + + $oMessage->SetFrom(new \MailSo\Mime\Email($oIdentity->Email(), $oIdentity->Name())); + + $oFrom = $oMessage->GetFrom(); + $oMessage->RegenerateMessageId($oFrom ? $oFrom->GetDomain() : ''); + + $sReplyTo = $oIdentity->ReplyTo(); + if (!empty($sReplyTo)) + { + $oReplyTo = new \MailSo\Mime\EmailCollection($sReplyTo); + if ($oReplyTo && $oReplyTo->Count()) + { + $oMessage->SetReplyTo($oReplyTo); + } + } + + $oMessage->SetSubject($sSubject); + + $oToEmails = new \MailSo\Mime\EmailCollection($sReadReceipt); + if ($oToEmails && $oToEmails->Count()) + { + $oMessage->SetTo($oToEmails); + } + + $this->Plugins()->RunHook('filter.read-receipt-message-plain', array($oAccount, $oMessage, &$sText)); + + $oMessage->AddText($sText, false); + + $this->Plugins()->RunHook('filter.build-read-receipt-message', array($oMessage, $oAccount)); + + return $oMessage; + } + + private function buildMessage(Account $oAccount, bool $bWithDraftInfo = true) : \MailSo\Mime\Message + { + $sIdentityID = $this->GetActionParam('IdentityID', ''); + $sTo = $this->GetActionParam('To', ''); + $sCc = $this->GetActionParam('Cc', ''); + $sBcc = $this->GetActionParam('Bcc', ''); + $sReplyTo = $this->GetActionParam('ReplyTo', ''); + $sSubject = $this->GetActionParam('Subject', ''); + $bTextIsHtml = '1' === $this->GetActionParam('TextIsHtml', '0'); + $bReadReceiptRequest = '1' === $this->GetActionParam('ReadReceiptRequest', '0'); + $bMarkAsImportant = '1' === $this->GetActionParam('MarkAsImportant', '0'); + $sText = $this->GetActionParam('Text', ''); + $aAttachments = $this->GetActionParam('Attachments', null); + + $aDraftInfo = $this->GetActionParam('DraftInfo', null); + $sInReplyTo = $this->GetActionParam('InReplyTo', ''); + $sReferences = $this->GetActionParam('References', ''); + + $oMessage = new \MailSo\Mime\Message(); + + if (!$this->Config()->Get('security', 'hide_x_mailer_header', true)) + { + $oMessage->SetXMailer('SnappyMail/'.APP_VERSION); + } else { + $oMessage->DoesNotAddDefaultXMailer(); + } + + $oFromIdentity = $this->GetIdentityByID($oAccount, $sIdentityID); + if ($oFromIdentity) + { + $oMessage->SetFrom(new \MailSo\Mime\Email( + $oFromIdentity->Email(), $oFromIdentity->Name())); + } + else + { + $oMessage->SetFrom(\MailSo\Mime\Email::Parse($oAccount->Email())); + } + + $oFrom = $oMessage->GetFrom(); + $oMessage->RegenerateMessageId($oFrom ? $oFrom->GetDomain() : ''); + + if (!empty($sReplyTo)) + { + $oReplyTo = new \MailSo\Mime\EmailCollection($sReplyTo); + if ($oReplyTo && 0 < $oReplyTo->Count()) + { + $oMessage->SetReplyTo($oReplyTo); + } + } + + if ($bReadReceiptRequest) + { + $oMessage->SetReadReceipt($oAccount->Email()); + } + + if ($bMarkAsImportant) + { + $oMessage->SetPriority(\MailSo\Mime\Enumerations\MessagePriority::HIGH); + } + + $oMessage->SetSubject($sSubject); + + $oToEmails = new \MailSo\Mime\EmailCollection($sTo); + if ($oToEmails && $oToEmails->Count()) + { + $oMessage->SetTo($oToEmails); + } + + $oCcEmails = new \MailSo\Mime\EmailCollection($sCc); + if ($oCcEmails && $oCcEmails->Count()) + { + $oMessage->SetCc($oCcEmails); + } + + $oBccEmails = new \MailSo\Mime\EmailCollection($sBcc); + if ($oBccEmails && $oBccEmails->Count()) + { + $oMessage->SetBcc($oBccEmails); + } + + if ($bWithDraftInfo && \is_array($aDraftInfo) && !empty($aDraftInfo[0]) && !empty($aDraftInfo[1]) && !empty($aDraftInfo[2])) + { + $oMessage->SetDraftInfo($aDraftInfo[0], $aDraftInfo[1], $aDraftInfo[2]); + } + + if (0 < \strlen($sInReplyTo)) + { + $oMessage->SetInReplyTo($sInReplyTo); + } + + if (0 < \strlen($sReferences)) + { + $oMessage->SetReferences($sReferences); + } + + $aFoundedCids = array(); + $mFoundDataURL = array(); + $aFoundedContentLocationUrls = array(); + + $sTextToAdd = $bTextIsHtml ? + \MailSo\Base\HtmlUtils::BuildHtml($sText, $aFoundedCids, $mFoundDataURL, $aFoundedContentLocationUrls) : $sText; + + $this->Plugins()->RunHook($bTextIsHtml ? 'filter.message-html' : 'filter.message-plain', + array($oAccount, $oMessage, &$sTextToAdd)); + + if ($bTextIsHtml && 0 < \strlen($sTextToAdd)) + { + $sTextConverted = \MailSo\Base\HtmlUtils::ConvertHtmlToPlain($sTextToAdd); + $this->Plugins()->RunHook('filter.message-plain', array($oAccount, $oMessage, &$sTextConverted)); + $oMessage->AddText($sTextConverted, false); + } + + $oMessage->AddText($sTextToAdd, $bTextIsHtml); + + if (\is_array($aAttachments)) + { + foreach ($aAttachments as $sTempName => $aData) + { + $sFileName = (string) $aData[0]; + $bIsInline = (bool) $aData[1]; + $sCID = (string) $aData[2]; + $sContentLocation = isset($aData[3]) ? (string) $aData[3] : ''; + + $rResource = $this->FilesProvider()->GetFile($oAccount, $sTempName); + if (\is_resource($rResource)) + { + $iFileSize = $this->FilesProvider()->FileSize($oAccount, $sTempName); + + $oMessage->Attachments()->append( + new \MailSo\Mime\Attachment($rResource, $sFileName, $iFileSize, $bIsInline, + \in_array(trim(trim($sCID), '<>'), $aFoundedCids), + $sCID, array(), $sContentLocation + ) + ); + } + } + } + + if ($mFoundDataURL && \is_array($mFoundDataURL) && 0 < \count($mFoundDataURL)) + { + foreach ($mFoundDataURL as $sCidHash => $sDataUrlString) + { + $aMatch = array(); + $sCID = '<'.$sCidHash.'>'; + if (\preg_match('/^data:(image\/[a-zA-Z0-9]+);base64,(.+)$/i', $sDataUrlString, $aMatch) && + !empty($aMatch[1]) && !empty($aMatch[2])) + { + $sRaw = \MailSo\Base\Utils::Base64Decode($aMatch[2]); + $iFileSize = \strlen($sRaw); + if (0 < $iFileSize) + { + $sFileName = \preg_replace('/[^a-z0-9]+/i', '.', $aMatch[1]); + $rResource = \MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString($sRaw); + + $sRaw = ''; + unset($sRaw); + unset($aMatch); + + $oMessage->Attachments()->append( + new \MailSo\Mime\Attachment($rResource, $sFileName, $iFileSize, true, true, $sCID) + ); + } + } + } + } + + $this->Plugins()->RunHook('filter.build-message', array($oMessage)); + + return $oMessage; + } } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php new file mode 100644 index 000000000..5566d350c --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php @@ -0,0 +1,420 @@ +initMailClientConnection(); + + $sRawKey = (string) $this->GetActionParam('RawKey', ''); + $aValues = $this->getDecodedRawKeyValue($sRawKey); + + $sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : ''; + $iUid = (int) (isset($aValues['Uid']) ? $aValues['Uid'] : 0); + $sMimeIndex = (string) (isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : ''); + + \header('Content-Type: text/plain', true); + + return $this->MailClient()->MessageMimeStream(function ($rResource) { + if (\is_resource($rResource)) + { + \MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource); + } + }, $sFolder, $iUid, true, $sMimeIndex); + } + + public function RawDownload() : bool + { + return $this->rawSmart(true); + } + + public function RawView() : bool + { + return $this->rawSmart(false); + } + + public function RawViewThumbnail() : bool + { + return $this->rawSmart(false, true); + } + + public function RawUserBackground() : bool + { + $sRawKey = (string) $this->GetActionParam('RawKey', ''); + $this->verifyCacheByKey($sRawKey); + + $oAccount = $this->getAccountFromToken(); + + $sData = $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'))) + { + $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); + + return true; + } + } + + return false; + } + + public function RawContactsVcf() : bool + { + $oAccount = $this->getAccountFromToken(); + + \header('Content-Type: text/x-vcard; charset=UTF-8'); + \header('Content-Disposition: attachment; filename="contacts.vcf"', true); + \header('Accept-Ranges: none', true); + \header('Content-Transfer-Encoding: binary'); + + $this->oHttp->ServerNoCache(); + + return $this->AddressBookProvider($oAccount)->IsActive() ? + $this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'vcf') : false; + } + + public function RawContactsCsv() : bool + { + $oAccount = $this->getAccountFromToken(); + + \header('Content-Type: text/csv; charset=UTF-8'); + \header('Content-Disposition: attachment; filename="contacts.csv"', true); + \header('Accept-Ranges: none', true); + \header('Content-Transfer-Encoding: binary'); + + $this->oHttp->ServerNoCache(); + + return $this->AddressBookProvider($oAccount)->IsActive() ? + $this->AddressBookProvider($oAccount)->Export($oAccount->ParentEmailHelper(), 'csv') : false; + } + + private function rawSmart(bool $bDownload, bool $bThumbnail = false) : bool + { + $sRawKey = (string) $this->GetActionParam('RawKey', ''); + $aValues = $this->getDecodedRawKeyValue($sRawKey); + + $sRange = $this->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)) + { + $sRangeStart = $aMatch[1]; + $sRangeEnd = $aMatch[2]; + + $bIsRangeRequest = true; + } + + $sFolder = isset($aValues['Folder']) ? $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'] : ''; + $sFileHashIn = isset($aValues['FileHash']) ? (string) $aValues['FileHash'] : ''; + + $bDetectImageOrientation = !!$this->Config()->Get('labs', 'detect_image_exif_orientation', true); + + if (!empty($sFileHashIn)) + { + $this->verifyCacheByKey($sRawKey); + + $oAccount = $this->getAccountFromToken(); + + $sContentTypeOut = empty($sContentTypeIn) ? + \MailSo\Base\Utils::MimeContentType($sFileNameIn) : $sContentTypeIn; + + $sFileNameOut = $this->MainClearFileName($sFileNameIn, $sContentTypeIn, $sMimeIndex); + + $rResource = $this->FilesProvider()->GetFile($oAccount, $sFileHashIn); + if (\is_resource($rResource)) + { + \header('Content-Type: '.$sContentTypeOut); + \header('Content-Disposition: attachment; '. + \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)), true); + + \header('Accept-Ranges: none', true); + \header('Content-Transfer-Encoding: binary'); + + \MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource); + return true; + } + + return false; + } + else + { + if (!empty($sFolder) && 0 < $iUid) + { + $this->verifyCacheByKey($sRawKey); + } + } + + $oAccount = $this->initMailClientConnection(); + + $self = $this; + return $this->MailClient()->MessageMimeStream( + function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use ( + $self, $oAccount, $sRawKey, $sContentTypeIn, $sFileNameIn, $bDownload, $bThumbnail, $bDetectImageOrientation, + $bIsRangeRequest, $sRangeStart, $sRangeEnd + ) { + if ($oAccount && \is_resource($rResource)) + { + \MailSo\Base\Utils::ResetTimeLimit(); + + $sContentTypeOut = $sContentTypeIn; + if (empty($sContentTypeOut)) + { + $sContentTypeOut = $sContentType; + if (empty($sContentTypeOut)) + { + $sContentTypeOut = (empty($sFileName)) ? 'text/plain' : \MailSo\Base\Utils::MimeContentType($sFileName); + } + } + + $sFileNameOut = $sFileNameIn; + if (empty($sFileNameOut)) + { + $sFileNameOut = $sFileName; + } + + $sFileNameOut = $self->MainClearFileName($sFileNameOut, $sContentTypeOut, $sMimeIndex); + + $self->cacheByKey($sRawKey); + + $sLoadedData = null; + if (!$bDownload) + { + if ($bThumbnail) + { + try + { + $oImagine = new \Imagine\Gd\Imagine(); + + $oImage = $oImagine->load(\stream_get_contents($rResource)); + + $oImage = $self->correctImageOrientation($oImage, $bDetectImageOrientation, 60); + + \header('Content-Disposition: inline; '. + \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut.'_thumb60x60.png')), true); + + $oImage->show('png'); + } + catch (\Throwable $oException) + { + $self->Logger()->WriteExceptionShort($oException); + } + } + else if ($bDetectImageOrientation && + \in_array($sContentTypeOut, array('image/png', 'image/jpeg', 'image/jpg')) && + \MailSo\Base\Utils::FunctionExistsAndEnabled('gd_info')) + { + try + { + $oImagine = new \Imagine\Gd\Imagine(); + + $sLoadedData = \stream_get_contents($rResource); + + $oImage = $oImagine->load($sLoadedData); + + $oImage = $self->correctImageOrientation($oImage, $bDetectImageOrientation); + + \header('Content-Disposition: inline; '. + \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)), true); + + $oImage->show($sContentTypeOut === 'image/png' ? 'png' : 'jpg'); + } + catch (\Throwable $oException) + { + $self->Logger()->WriteExceptionShort($oException); + } + } + else + { + $sLoadedData = \stream_get_contents($rResource); + } + } + + if ($bDownload || $sLoadedData) + { + if (!headers_sent()) { + \header('Content-Type: '.$sContentTypeOut); + \header('Content-Disposition: '.($bDownload ? 'attachment' : 'inline').'; '. + \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)), true); + + \header('Accept-Ranges: bytes'); + \header('Content-Transfer-Encoding: binary'); + } + + if ($bIsRangeRequest && !$sLoadedData) + { + $sLoadedData = \stream_get_contents($rResource); + } + + \MailSo\Base\Utils::ResetTimeLimit(); + + if ($sLoadedData) + { + if ($bIsRangeRequest && (0 < \strlen($sRangeStart) || 0 < \strlen($sRangeEnd))) + { + $iFullContentLength = \strlen($sLoadedData); + + $self->Http()->StatusHeader(206); + + $iRangeStart = (int) $sRangeStart; + $iRangeEnd = (int) $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; + } + } + + $iContentLength = \strlen($sLoadedData); + + if (0 < $iContentLength) + { + \header('Content-Length: '.$iContentLength, true); + \header('Content-Range: bytes '.$sRangeStart.'-'.(0 < $iRangeEnd ? $iRangeEnd : $iFullContentLength - 1).'/'.$iFullContentLength); + } + + echo $sLoadedData; + } + else + { + echo $sLoadedData; + } + + unset($sLoadedData); + } + else + { + \MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource); + } + } + } + }, $sFolder, $iUid, true, $sMimeIndex); + } + + private function correctImageOrientation(\Imagine\Image\AbstractImage $oImage, bool $bDetectImageOrientation = true, int $iThumbnailBoxSize = 0) : \Imagine\Image\AbstractImage + { + $iOrientation = 1; + if ($bDetectImageOrientation && \MailSo\Base\Utils::FunctionExistsAndEnabled('exif_read_data') && + \MailSo\Base\Utils::FunctionExistsAndEnabled('gd_info')) + { + $oMetadata = $oImage->metadata(new \Imagine\Image\Metadata\ExifMetadataReader()); + $iOrientation = isset($oMetadata['ifd0.Orientation']) && + is_numeric($oMetadata['ifd0.Orientation']) ? (int) $oMetadata['ifd0.Orientation'] : 1; + } + + if (0 < $iThumbnailBoxSize) { + $oImage = $oImage->thumbnail( + new \Imagine\Image\Box($iThumbnailBoxSize, $iThumbnailBoxSize), + \Imagine\Image\ImageInterface::THUMBNAIL_OUTBOUND); + } + + // rotateImageByOrientation + if (0 < $iOrientation) { + switch ($iOrientation) + { + case 2: // flip horizontal + $oImage->flipHorizontally(); + break; + + case 3: // rotate 180 + $oImage->rotate(180); + break; + + case 4: // flip vertical + $oImage->flipVertically(); + break; + + case 5: // vertical flip + 90 rotate + $oImage->flipVertically(); + $oImage->rotate(90); + break; + + case 6: // rotate 90 + $oImage->rotate(90); + break; + + case 7: // horizontal flip + 90 rotate + $oImage->flipHorizontally(); + $oImage->rotate(90); + break; + + case 8: // rotate 270 + $oImage->rotate(270); + break; + } + } + + return $oImage; + } +} diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php index 22c4fc294..c1f850240 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php @@ -2,9 +2,9 @@ namespace RainLoop\Actions; +use \RainLoop\Enumerations\Capa; use \RainLoop\Notifications; use \RainLoop\Utils; -use \RainLoop\Enumerations\Capa; trait Response { @@ -112,15 +112,6 @@ trait Response return $aResult; } -/* - $this->Cacher( - $this->Config( - $this->getAccountFromToken( - $this->GetCapa( - $this->MailClient( - $this->SettingsProvider( - $this->Plugins( -*/ private function isFileHasFramedPreview(string $sFileName) : bool { $sExt = \MailSo\Base\Utils::GetFileExtension($sFileName); @@ -168,50 +159,6 @@ trait Response $sFolderFullName : \md5($sFolderFullName); } -/* - public function RawFramedView() : string - { - $oAccount = $this->getAccountFromToken(false); - if ($oAccount) - { - $sRawKey = (string) $this->GetActionParam('RawKey', ''); - $aParams = $this->GetActionParam('Params', null); - $this->Http()->ServerNoCache(); - - $aData = Utils::DecodeKeyValuesQ($sRawKey); - if (isset($aParams[0], $aParams[1], $aParams[2]) && - 'Raw' === $aParams[0] && 'FramedView' === $aParams[2] && isset($aData['Framed']) && $aData['Framed'] && $aData['FileName']) - { - if ($this->isFileHasFramedPreview($aData['FileName'])) - { - $sNewSpecAuthToken = $this->GetShortLifeSpecAuthToken(); - if (!empty($sNewSpecAuthToken)) - { - $aParams[1] = '_'.$sNewSpecAuthToken; - $aParams[2] = 'View'; - - \array_shift($aParams); - $sLast = \array_pop($aParams); - - $sUrl = $this->Http()->GetFullUrl().'?/Raw/&q[]=/'.\implode('/', $aParams).'/&q[]=/'.$sLast; - $sFullUrl = 'https://docs.google.com/viewer?embedded=true&url='.\urlencode($sUrl); - - \header('Content-Type: text/html; charset=utf-8'); - echo ''. - ''. - ''. - ''; - } - } - } - } - - - return true; - - } -*/ - private function objectData(object $oData, string $sParent, array $aParameters = array()) : array { $mResult = array(); @@ -633,4 +580,48 @@ trait Response return $mResponse; } + + private function explodeSubject(string $sSubject) : array + { + $aResult = array('', ''); + if (0 < \strlen($sSubject)) + { + $bDrop = false; + $aPrefix = array(); + $aSuffix = array(); + + $aParts = \explode(':', $sSubject); + foreach ($aParts as $sPart) + { + if (!$bDrop && + (\preg_match('/^(RE|FWD)$/i', \trim($sPart)) || \preg_match('/^(RE|FWD)[\[\(][\d]+[\]\)]$/i', \trim($sPart)))) + { + $aPrefix[] = $sPart; + } + else + { + $aSuffix[] = $sPart; + $bDrop = true; + } + } + + if (0 < \count($aPrefix)) + { + $aResult[0] = \rtrim(\trim(\implode(':', $aPrefix)), ':').': '; + } + + if (0 < \count($aSuffix)) + { + $aResult[1] = \trim(\implode(':', $aSuffix)); + } + + if (0 === \strlen($aResult[1])) + { + $aResult = array('', $sSubject); + } + } + + return $aResult; + } + } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Templates.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Templates.php index 158372a62..399271f85 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Templates.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Templates.php @@ -4,6 +4,8 @@ namespace RainLoop\Actions; use \RainLoop\Enumerations\Capa; use \RainLoop\Exceptions\ClientException; +use \RainLoop\Model\Account; +use \RainLoop\Model\Template; use \RainLoop\Notifications; trait Templates @@ -21,7 +23,7 @@ trait Templates return $this->FalseResponse(__FUNCTION__); } - $oTemplate = new \RainLoop\Model\Template(); + $oTemplate = new Template(); if (!$oTemplate->FromJSON($this->GetActionParams(), true)) { throw new ClientException(Notifications::InvalidInputArgument); @@ -130,4 +132,84 @@ trait Templates 'Templates' => $this->GetTemplates($oAccount) )); } + + private function GetTemplates(?Account $oAccount) : array + { + $aTemplates = array(); + if ($oAccount) + { + $aData = array(); + + $sData = $this->StorageProvider(true)->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'templates' + ); + + if ('' !== $sData && '[' === \substr($sData, 0, 1)) + { + $aData = \json_decode($sData, true); + } + + if (\is_array($aData) && 0 < \count($aData)) + { + foreach ($aData as $aItem) + { + $oItem = new Template(); + $oItem->FromJSON($aItem); + + if ($oItem && $oItem->Validate()) + { + \array_push($aTemplates, $oItem); + } + } + } + + if (1 < \count($aTemplates)) + { + $sOrder = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'templates_order' + ); + + $aOrder = empty($sOrder) ? array() : \json_decode($sOrder, true); + if (\is_array($aOrder) && 1 < \count($aOrder)) + { + \usort($aTemplates, function ($a, $b) use ($aOrder) { + return \array_search($a->Id(), $aOrder) < \array_search($b->Id(), $aOrder) ? -1 : 1; + }); + } + } + } + + return $aTemplates; + } + + private function GetTemplateByID(Account $oAccount, string $sID) : ?\RainLoop\Model\Identity + { + $aTemplates = $this->GetTemplates($oAccount); + foreach ($aTemplates as $oIdentity) + { + if ($oIdentity && $sID === $oIdentity->Id()) + { + return $oIdentity; + } + } + + return isset($aTemplates[0]) ? $aTemplates[0] : null; + } + + private function SetTemplates(Account $oAccount, array $aTemplates = array()) : array + { + $aResult = array(); + foreach ($aTemplates as $oItem) + { + $aResult[] = $oItem->ToSimpleJSON(); + } + + return $this->StorageProvider(true)->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'templates', + \json_encode($aResult) + ); + } } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/TwoFactor.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/TwoFactor.php index adfbe8df9..f8a288419 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/TwoFactor.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/TwoFactor.php @@ -2,15 +2,21 @@ namespace RainLoop\Actions; +use \RainLoop\Enumerations\Capa; + trait TwoFactor { + /** + * @var \RainLoop\Providers\TwoFactorAuth + */ + private $oTwoFactorAuthProvider; public function DoGetTwoFactorInfo() : array { $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || - !$this->GetCapa(false, false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) + !$this->GetCapa(false, false, Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } @@ -24,7 +30,7 @@ trait TwoFactor $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || - !$this->GetCapa(false, false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) + !$this->GetCapa(false, false, Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } @@ -61,7 +67,7 @@ trait TwoFactor $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || - !$this->GetCapa(false, false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) + !$this->GetCapa(false, false, Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } @@ -77,7 +83,7 @@ trait TwoFactor $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || - !$this->GetCapa(false, false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) + !$this->GetCapa(false, false, Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } @@ -108,7 +114,7 @@ trait TwoFactor $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || - !$this->GetCapa(false, false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) + !$this->GetCapa(false, false, Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } @@ -134,7 +140,7 @@ trait TwoFactor $oAccount = $this->getAccountFromToken(); if (!$this->TwoFactorAuthProvider()->IsActive() || - !$this->GetCapa(false, false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) + !$this->GetCapa(false, false, Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } @@ -148,4 +154,109 @@ trait TwoFactor $this->getTwoFactorInfo($oAccount, true)); } + protected function TwoFactorAuthProvider() : \RainLoop\Providers\TwoFactorAuth + { + if (!$this->oTwoFactorAuthProvider) { + $this->oTwoFactorAuthProvider = new \RainLoop\Providers\TwoFactorAuth( + $this->Config()->Get('security', 'allow_two_factor_auth', false) ? $this->fabrica('two-factor-auth') : null + ); + } + return $this->oTwoFactorAuthProvider; + } + + protected function getTwoFactorInfo(\RainLoop\Model\Account $oAccount, bool $bRemoveSecret = false) : array + { + $sEmail = $oAccount->ParentEmailHelper(); + + $mData = null; + + $aResult = array( + 'User' => '', + 'IsSet' => false, + 'Enable' => false, + 'Secret' => '', + 'UrlTitle' => '', + 'BackupCodes' => '' + ); + + if (!empty($sEmail)) + { + $aResult['User'] = $sEmail; + + $sData = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor' + ); + + if ($sData) + { + $mData = \RainLoop\Utils::DecodeKeyValues($sData); + } + } + + if (!empty($aResult['User']) && + !empty($mData['User']) && !empty($mData['Secret']) && + !empty($mData['BackupCodes']) && $sEmail === $mData['User']) + { + $aResult['IsSet'] = true; + $aResult['Enable'] = isset($mData['Enable']) ? !!$mData['Enable'] : false; + $aResult['Secret'] = $mData['Secret']; + $aResult['BackupCodes'] = $mData['BackupCodes']; + $aResult['UrlTitle'] = $this->Config()->Get('webmail', 'title', ''); + } + + if ($bRemoveSecret) + { + if (isset($aResult['Secret'])) + { + unset($aResult['Secret']); + } + + if (isset($aResult['UrlTitle'])) + { + unset($aResult['UrlTitle']); + } + + if (isset($aResult['BackupCodes'])) + { + unset($aResult['BackupCodes']); + } + } + + return $aResult; + } + + protected function removeBackupCodeFromTwoFactorInfo(\RainLoop\Model\Account $oAccount, string $sCode) : bool + { + if (!$oAccount || empty($sCode)) + { + return false; + } + + $sData = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor' + ); + + if ($sData) + { + $mData = \RainLoop\Utils::DecodeKeyValues($sData); + + if (!empty($mData['BackupCodes'])) + { + $sBackupCodes = \preg_replace('/[^\d]+/', ' ', ' '.$mData['BackupCodes'].' '); + $sBackupCodes = \str_replace(' '.$sCode.' ', '', $sBackupCodes); + + $mData['BackupCodes'] = \trim(\preg_replace('/[^\d]+/', ' ', $sBackupCodes)); + + return $this->StorageProvider()->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor', + \RainLoop\Utils::EncodeKeyValues($mData) + ); + } + } + + return false; + } } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php index 3df9f4405..93a27e587 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php @@ -2,6 +2,11 @@ namespace RainLoop\Actions; +use \RainLoop\Enumerations\Capa; +use \RainLoop\Exceptions\ClientException; +use \RainLoop\Notifications; +use \RainLoop\Utils; + trait User { use Accounts; @@ -35,7 +40,7 @@ trait User ) { \sleep((int) $sPassword); - throw new Exceptions\ClientException(Notifications::AuthError); + throw new ClientException(Notifications::AuthError); } try @@ -44,7 +49,7 @@ trait User $bSignMe ? $this->generateSignMeToken($sEmail) : '', $sAdditionalCode, $bAdditionalCodeSignMe); } - catch (Exceptions\ClientException $oException) + catch (ClientException $oException) { if ($oException && Notifications::AccountTwoFactorAuthRequired === $oException->getCode()) @@ -85,7 +90,7 @@ trait User */ public function DoAttachmentsActions() : array { - if (!$this->GetCapa(false, false, Enumerations\Capa::ATTACHMENTS_ACTIONS)) + if (!$this->GetCapa(false, false, Capa::ATTACHMENTS_ACTIONS)) { return $this->FalseResponse(__FUNCTION__); } @@ -211,7 +216,7 @@ trait User if (!$oAccount->IsAdditionalAccount()) { - Utils::ClearCookie(Actions::AUTH_SPEC_TOKEN_KEY); + Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY); } } @@ -233,7 +238,7 @@ trait User $iOneDay3 = 60 * 60 * 30; $sTimers = $this->StorageProvider()->Get(null, - Providers\Storage\Enumerations\StorageType::NOBODY, 'Cache/Timers', ''); + \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, 'Cache/Timers', ''); $aTimers = \explode(',', $sTimers); @@ -262,7 +267,7 @@ trait User if ($bMainCache || $bFilesCache || $bVersionsCache) { if (!$this->StorageProvider()->Put(null, - Providers\Storage\Enumerations\StorageType::NOBODY, 'Cache/Timers', + \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, 'Cache/Timers', \implode(',', array($iMainCacheTime, $iFilesCacheTime, $iVersionsCacheTime)))) { $bMainCache = $bFilesCache = $bVersionsCache = false; @@ -290,7 +295,7 @@ trait User public function DoSettingsUpdate() : array { $oAccount = $this->getAccountFromToken(); - if (!$this->GetCapa(false, false, Enumerations\Capa::SETTINGS, $oAccount)) + if (!$this->GetCapa(false, false, Capa::SETTINGS, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } @@ -312,7 +317,7 @@ trait User $oSettings->SetConf('Language', $this->ValidateLanguage($oConfig->Get('webmail', 'language', 'en'))); } - if ($this->GetCapa(false, false, Enumerations\Capa::THEMES, $oAccount)) + if ($this->GetCapa(false, false, Capa::THEMES, $oAccount)) { $this->setSettingsFromParams($oSettingsLocal, 'Theme', 'string', function ($sTheme) use ($self) { return $self->ValidateTheme($sTheme); @@ -328,9 +333,9 @@ trait User }); $this->setSettingsFromParams($oSettings, 'Layout', 'int', function ($iValue) { - return (int) (\in_array((int) $iValue, array(Enumerations\Layout::NO_PREVIW, - Enumerations\Layout::SIDE_PREVIEW, Enumerations\Layout::BOTTOM_PREVIEW)) ? - $iValue : Enumerations\Layout::SIDE_PREVIEW); + return (int) (\in_array((int) $iValue, array(\RainLoop\Enumerations\Layout::NO_PREVIW, + \RainLoop\Enumerations\Layout::SIDE_PREVIEW, \RainLoop\Enumerations\Layout::BOTTOM_PREVIEW)) ? + $iValue : \RainLoop\Enumerations\Layout::SIDE_PREVIEW); }); $this->setSettingsFromParams($oSettings, 'EditorDefaultType', 'string'); @@ -356,7 +361,7 @@ trait User { $oAccount = $this->initMailClientConnection(); - if (!$this->GetCapa(false, false, Enumerations\Capa::QUOTA, $oAccount)) + if (!$this->GetCapa(false, false, Capa::QUOTA, $oAccount)) { return $this->DefaultResponse(__FUNCTION__, array(0, 0, 0, 0)); } @@ -367,7 +372,7 @@ trait User } catch (\Throwable $oException) { - throw new Exceptions\ClientException(Notifications::MailServerError, $oException); + throw new ClientException(Notifications::MailServerError, $oException); } return $this->DefaultResponse(__FUNCTION__, $aQuota); @@ -489,7 +494,7 @@ trait User { $oAccount = $this->getAccountFromToken(); - if (!$this->GetCapa(false, false, Enumerations\Capa::USER_BACKGROUND, $oAccount)) + if (!$this->GetCapa(false, false, Capa::USER_BACKGROUND, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } @@ -498,7 +503,7 @@ trait User if ($oAccount && $oSettings) { $this->StorageProvider()->Clear($oAccount, - Providers\Storage\Enumerations\StorageType::CONFIG, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'background' ); @@ -510,4 +515,96 @@ trait User $this->SettingsProvider()->Save($oAccount, $oSettings) : false); } + protected function ClearSignMeData(Model\Account $oAccount) : void + { + if ($oAccount) { + Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY); + $this->StorageProvider()->Clear($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'sign_me' + ); + } + } + + private function generateSignMeToken(string $sEmail) : string + { + return \MailSo\Base\Utils::Md5Rand(APP_SALT.$sEmail); + } + + private function getMimeFileByHash(\RainLoop\Model\Account $oAccount, string $sHash) : array + { + $aValues = $this->getDecodedRawKeyValue($sHash); + + $sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : ''; + $iUid = (int) isset($aValues['Uid']) ? $aValues['Uid'] : 0; + $sMimeIndex = (string) isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : ''; + + $sContentTypeIn = (string) isset($aValues['MimeType']) ? $aValues['MimeType'] : ''; + $sFileNameIn = (string) isset($aValues['FileName']) ? $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::Md5Rand($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, true, $sMimeIndex); + + $aValues['FileHash'] = ''; + if ($mResult) + { + $aValues['FileHash'] = $sResultHash; + } + + return $aValues; + } + + private function setSettingsFromParams(\RainLoop\Settings $oSettings, string $sConfigName, string $sType = 'string', ?callable $mStringCallback = null) : void + { + if ($this->HasActionParam($sConfigName)) + { + $sValue = $this->GetActionParam($sConfigName, ''); + switch ($sType) + { + default: + case 'string': + $sValue = (string) $sValue; + if ($mStringCallback && is_callable($mStringCallback)) + { + $sValue = call_user_func($mStringCallback, $sValue); + } + + $oSettings->SetConf($sConfigName, (string) $sValue); + break; + + case 'int': + $iValue = (int) $sValue; + $oSettings->SetConf($sConfigName, $iValue); + break; + + case 'bool': + $oSettings->SetConf($sConfigName, '1' === (string) $sValue); + break; + } + } + } }