Added support for rfc7889 and impoved rfc8474

This commit is contained in:
djmaze 2021-10-31 00:33:22 +02:00
parent d4117dc723
commit e4149b655c
6 changed files with 105 additions and 46 deletions

View file

@ -26,4 +26,8 @@ abstract class FolderResponseStatus
const UNSEEN = 'UNSEEN'; const UNSEEN = 'UNSEEN';
// rfc4551 // rfc4551
const HIGHESTMODSEQ = 'HIGHESTMODSEQ'; const HIGHESTMODSEQ = 'HIGHESTMODSEQ';
// rfc7889
const APPENDLIMIT = 'APPENDLIMIT';
// rfc8474
const MAILBOXID = 'MAILBOXID';
} }

View file

@ -26,6 +26,8 @@ abstract class FolderStatus
const UNSEEN = 'UNSEEN'; const UNSEEN = 'UNSEEN';
// rfc4551 // rfc4551
const HIGHESTMODSEQ = 'HIGHESTMODSEQ'; const HIGHESTMODSEQ = 'HIGHESTMODSEQ';
// rfc7889
const APPENDLIMIT = 'APPENDLIMIT';
// rfc8474 // rfc8474
const MAILBOXID = 'MAILBOXID'; const MAILBOXID = 'MAILBOXID';
} }

View file

@ -54,12 +54,12 @@ class FetchResponse
{ {
$oResult = null; $oResult = null;
$aEmails = $this->GetFetchEnvelopeValue($iIndex); $aEmails = $this->GetFetchEnvelopeValue($iIndex);
if (is_array($aEmails) && 0 < count($aEmails)) if (\is_array($aEmails) && \count($aEmails))
{ {
$oResult = new \MailSo\Mime\EmailCollection; $oResult = new \MailSo\Mime\EmailCollection;
foreach ($aEmails as $aEmailItem) foreach ($aEmails as $aEmailItem)
{ {
if (is_array($aEmailItem) && 4 === count($aEmailItem)) if (\is_array($aEmailItem) && 4 === \count($aEmailItem))
{ {
$sDisplayName = \MailSo\Base\Utils::DecodeHeaderValue( $sDisplayName = \MailSo\Base\Utils::DecodeHeaderValue(
self::findEnvelopeIndex($aEmailItem, 0, ''), $sParentCharset); self::findEnvelopeIndex($aEmailItem, 0, ''), $sParentCharset);
@ -70,7 +70,7 @@ class FetchResponse
$sLocalPart = self::findEnvelopeIndex($aEmailItem, 2, ''); $sLocalPart = self::findEnvelopeIndex($aEmailItem, 2, '');
$sDomainPart = self::findEnvelopeIndex($aEmailItem, 3, ''); $sDomainPart = self::findEnvelopeIndex($aEmailItem, 3, '');
if (0 < strlen($sLocalPart) && 0 < strlen($sDomainPart)) if (\strlen($sLocalPart) && \strlen($sDomainPart))
{ {
$oResult->append( $oResult->append(
new \MailSo\Mime\Email($sLocalPart.'@'.$sDomainPart, $sDisplayName) new \MailSo\Mime\Email($sLocalPart.'@'.$sDomainPart, $sDisplayName)
@ -87,9 +87,9 @@ class FetchResponse
{ {
$aBodyStructureArray = $this->GetFetchValue(Enumerations\FetchType::BODYSTRUCTURE); $aBodyStructureArray = $this->GetFetchValue(Enumerations\FetchType::BODYSTRUCTURE);
if (is_array($aBodyStructureArray)) if (\is_array($aBodyStructureArray))
{ {
if (0 < strlen($sRfc822SubMimeIndex)) if (\strlen($sRfc822SubMimeIndex))
{ {
return BodyStructure::NewInstanceFromRfc822SubPart($aBodyStructureArray, $sRfc822SubMimeIndex); return BodyStructure::NewInstanceFromRfc822SubPart($aBodyStructureArray, $sRfc822SubMimeIndex);
} }
@ -100,6 +100,7 @@ class FetchResponse
} }
/** /**
* Like: UID, RFC822.SIZE, MODSEQ, INTERNALDATE, FLAGS, BODYSTRUCTURE
* @return mixed * @return mixed
*/ */
public function GetFetchValue(string $sFetchItemName) public function GetFetchValue(string $sFetchItemName)
@ -124,11 +125,15 @@ class FetchResponse
return null; return null;
} }
/**
* Like: BODY[HEADER.FIELDS (RETURN-PATH RECEIVED MIME-VERSION MESSAGE-ID CONTENT-TYPE FROM TO CC BCC SENDER REPLY-TO DELIVERED-TO IN-REPLY-TO REFERENCES DATE SUBJECT SENSITIVITY X-MSMAIL-PRIORITY IMPORTANCE X-PRIORITY X-DRAFT-INFO RETURN-RECEIPT-TO DISPOSITION-NOTIFICATION-TO X-CONFIRM-READING-TO AUTHENTICATION-RESULTS X-DKIM-AUTHENTICATION-RESULTS LIST-UNSUBSCRIBE X-SPAM-STATUS X-SPAMD-RESULT X-BOGOSITY X-VIRUS X-VIRUS-SCANNED X-VIRUS-STATUS)]
* @return mixed
*/
public function GetHeaderFieldsValue(string $sRfc822SubMimeIndex = '') : string public function GetHeaderFieldsValue(string $sRfc822SubMimeIndex = '') : string
{ {
$bNextIsValue = false; $bNextIsValue = false;
$sRfc822SubMimeIndex = 0 < \strlen($sRfc822SubMimeIndex) ? ''.$sRfc822SubMimeIndex.'.' : ''; $sRfc822SubMimeIndex = \strlen($sRfc822SubMimeIndex) ? ''.$sRfc822SubMimeIndex.'.' : '';
if (isset($this->oImapResponse->ResponseList[3]) && \is_array($this->oImapResponse->ResponseList[3])) if (isset($this->oImapResponse->ResponseList[3]) && \is_array($this->oImapResponse->ResponseList[3]))
{ {

View file

@ -334,6 +334,25 @@ class ImapClient extends \MailSo\Net\NetClient
} }
} }
/**
* RFC 7889
* APPENDLIMIT=<number> indicates that the IMAP server has the same upload limit for all mailboxes.
* APPENDLIMIT without any value indicates that the IMAP server supports this extension,
* and that the client will need to discover upload limits for each mailbox,
* as they might differ from mailbox to mailbox.
*/
public function AppendLimit() : ?int
{
if ($this->aCapabilityItems) {
foreach ($this->aCapabilityItems as $string) {
if ('APPENDLIMIT=' === \substr($string, 0, 12)) {
return (int) \substr($string, 12);
}
}
}
return null;
}
/** /**
* @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Net\Exceptions\Exception
* @throws \MailSo\Imap\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception
@ -416,21 +435,36 @@ class ImapClient extends \MailSo\Net\NetClient
*/ */
public function FolderStatus(string $sFolderName, array $aStatusItems) : ?array public function FolderStatus(string $sFolderName, array $aStatusItems) : ?array
{ {
if (!\count($aStatusItems)) {
return null;
}
$oFolderInfo = $this->oCurrentFolderInfo; $oFolderInfo = $this->oCurrentFolderInfo;
$bReselect = false;
$bWritable = false;
if ($oFolderInfo && $sFolderName === $oFolderInfo->FolderName) { if ($oFolderInfo && $sFolderName === $oFolderInfo->FolderName) {
// Not supported by Outlook/Hotmail/Office365 /**
* There's a long standing IMAP CLIENTBUG where STATUS command is executed
* after SELECT/EXAMINE on same folder (it should not).
* So we must unselect the folder to be able to get the APPENDLIMIT and UNSEEN.
*/
/*
if ($this->IsSupported('ESEARCH')) { if ($this->IsSupported('ESEARCH')) {
$aResult = $oFolderInfo->getStatusItems(); $aResult = $oFolderInfo->getStatusItems();
// SELECT or EXAMINE command then UNSEEN is the message sequence number of the first unseen message // SELECT or EXAMINE command then UNSEEN is the message sequence number of the first unseen message
$aResult['UNSEEN'] = $this->simpleESearchOrESortHelper(false, 'UNSEEN', ['COUNT'])['COUNT']; $aResult['UNSEEN'] = $this->simpleESearchOrESortHelper(false, 'UNSEEN', ['COUNT'])['COUNT'];
return $aResult; return $aResult;
} }
// $this->FolderUnSelect(); */
$bWritable = $oFolderInfo->IsWritable;
$bReselect = true;
$this->FolderUnSelect();
} }
return \count($aStatusItems) $aResult = $this->SendRequestGetResponse('STATUS', array($this->EscapeString($sFolderName), $aStatusItems))
? $this->SendRequestGetResponse('STATUS', array($this->EscapeString($sFolderName), $aStatusItems)) ->getStatusFolderInformationResult();
->getStatusFolderInformationResult() if ($bReselect) {
: null; $this->selectOrExamineFolder($sFolderName, $bWritable, false);
}
return $aResult;
} }
/** /**
@ -461,18 +495,26 @@ class ImapClient extends \MailSo\Net\NetClient
$aParameters[] = $this->EscapeString($sParentFolderName); $aParameters[] = $this->EscapeString($sParentFolderName);
$aParameters[] = $this->EscapeString(\strlen(\trim($sListPattern)) ? $sListPattern : '*'); $aParameters[] = $this->EscapeString(\strlen(\trim($sListPattern)) ? $sListPattern : '*');
// RFC 5819
if ($bUseListStatus && !$bIsSubscribeList && $this->IsSupported('LIST-STATUS')) if ($bUseListStatus && !$bIsSubscribeList && $this->IsSupported('LIST-STATUS'))
{ {
// RFC 5819
$aL = array( $aL = array(
Enumerations\FolderStatus::MESSAGES, Enumerations\FolderStatus::MESSAGES,
Enumerations\FolderStatus::UNSEEN, Enumerations\FolderStatus::UNSEEN,
Enumerations\FolderStatus::UIDNEXT Enumerations\FolderStatus::UIDNEXT
); );
// RFC 4551
if ($this->IsSupported('CONDSTORE')) { if ($this->IsSupported('CONDSTORE')) {
$aL[] = Enumerations\FolderStatus::HIGHESTMODSEQ; $aL[] = Enumerations\FolderStatus::HIGHESTMODSEQ;
} }
// RFC 7889
if ($this->IsSupported('APPENDLIMIT')) {
$aL[] = Enumerations\FolderStatus::APPENDLIMIT;
}
// RFC 8474
if ($this->IsSupported('OBJECTID')) {
$aTypes[] = Enumerations\FolderStatus::MAILBOXID;
}
$aReturnParams[] = 'STATUS'; $aReturnParams[] = 'STATUS';
$aReturnParams[] = $aL; $aReturnParams[] = $aL;
@ -560,11 +602,17 @@ class ImapClient extends \MailSo\Net\NetClient
throw new \MailSo\Base\Exceptions\InvalidArgumentException; throw new \MailSo\Base\Exceptions\InvalidArgumentException;
} }
$aParams = array($this->EscapeString($sFolderName));
/*
if ($this->IsSupported('CONDSTORE')) {
$aParams[] = ['CONDSTORE'];
}
*/
/** /**
* IMAP4rev2 SELECT/EXAMINE are now required to return an untagged LIST response. * IMAP4rev2 SELECT/EXAMINE are now required to return an untagged LIST response.
*/ */
$this->oCurrentFolderInfo = $this->SendRequestGetResponse($bIsWritable ? 'SELECT' : 'EXAMINE', $this->oCurrentFolderInfo = $this->SendRequestGetResponse($bIsWritable ? 'SELECT' : 'EXAMINE',
array($this->EscapeString($sFolderName))) $aParams)
->getCurrentFolderInformation($sFolderName, $bIsWritable); ->getCurrentFolderInformation($sFolderName, $bIsWritable);
return $this; return $this;

View file

@ -70,6 +70,13 @@ trait Status
*/ */
$HIGHESTMODSEQ, $HIGHESTMODSEQ,
/**
* RFC7889
* Message upload size limit.
* @var int
*/
$APPENDLIMIT,
/** /**
* RFC8474 * RFC8474
* A server-allocated unique identifier for the mailbox. * A server-allocated unique identifier for the mailbox.
@ -81,7 +88,7 @@ trait Status
public function getStatusItems() : array public function getStatusItems() : array
{ {
return \array_filter(\get_object_vars($this), function($v, $k){ return \array_filter(\get_object_vars($this), function($v, $k){
return isset($v) && \property_exists(__TRAIT__, $k); return \property_exists(__TRAIT__, $k);
}, ARRAY_FILTER_USE_BOTH); }, ARRAY_FILTER_USE_BOTH);
} }

View file

@ -519,8 +519,7 @@ class MailClient
return $this; return $this;
} }
protected function initFolderValues(string $sFolderName, int &$iCount, int &$iUnseenCount, protected function initFolderValues(string $sFolderName) : array
int &$iUidNext, int &$iHighestModSeq) : void
{ {
$aTypes = array( $aTypes = array(
FolderResponseStatus::MESSAGES, FolderResponseStatus::MESSAGES,
@ -528,24 +527,31 @@ class MailClient
FolderResponseStatus::UIDNEXT FolderResponseStatus::UIDNEXT
); );
if ($this->oImapClient->IsSupported('CONDSTORE')) if ($this->oImapClient->IsSupported('CONDSTORE')) {
{
$aTypes[] = FolderResponseStatus::HIGHESTMODSEQ; $aTypes[] = FolderResponseStatus::HIGHESTMODSEQ;
} }
if ($this->oImapClient->IsSupported('APPENDLIMIT')) {
$aTypes[] = FolderResponseStatus::APPENDLIMIT;
}
if ($this->oImapClient->IsSupported('OBJECTID')) {
$aTypes[] = FolderResponseStatus::MAILBOXID;
}
$aFolderStatus = $this->oImapClient->FolderStatus($sFolderName, $aTypes); $aFolderStatus = $this->oImapClient->FolderStatus($sFolderName, $aTypes);
$iCount = (int) $aFolderStatus[FolderResponseStatus::MESSAGES] ?? 0; return [
$aFolderStatus[FolderResponseStatus::MESSAGES] ?: 0,
$iUnseenCount = (int) $aFolderStatus[FolderResponseStatus::UNSEEN] ?? 0; $aFolderStatus[FolderResponseStatus::UNSEEN] ?: 0,
$iUidNext = (int) $aFolderStatus[FolderResponseStatus::UIDNEXT] ?? 0; $aFolderStatus[FolderResponseStatus::UIDNEXT] ?: 0,
if (isset($aFolderStatus[FolderResponseStatus::HIGHESTMODSEQ])) { $aFolderStatus[FolderResponseStatus::HIGHESTMODSEQ] ?: 0,
$iHighestModSeq = (int) $aFolderStatus[FolderResponseStatus::HIGHESTMODSEQ];
} else { $aFolderStatus[FolderResponseStatus::APPENDLIMIT] ?: $this->oImapClient->AppendLimit(),
$iHighestModSeq = 0;
} $aFolderStatus[FolderResponseStatus::MAILBOXID] ?: ''
];
} }
public function GenerateImapClientHash() : string public function GenerateImapClientHash() : string
@ -652,12 +658,7 @@ class MailClient
} }
} }
$iCount = 0; list($iCount, $iUnseenCount, $iUidNext, $iHighestModSeq, $iAppendLimit, $sMailboxId) = $this->initFolderValues($sFolderName);
$iUnseenCount = 0;
$iUidNext = 0;
$iHighestModSeq = 0;
$this->initFolderValues($sFolderName, $iCount, $iUnseenCount, $iUidNext, $iHighestModSeq);
return array( return array(
'Folder' => $sFolderName, 'Folder' => $sFolderName,
@ -667,6 +668,8 @@ class MailClient
'UidNext' => $iUidNext, 'UidNext' => $iUidNext,
'MessageFlags' => $aFlags, 'MessageFlags' => $aFlags,
'HighestModSeq' => $iHighestModSeq, 'HighestModSeq' => $iHighestModSeq,
'AppendLimit' => $iAppendLimit,
'MailboxId' => $sMailboxId,
'NewMessages' => 'INBOX' === $sFolderName && \MailSo\Config::$CheckNewMessages ? 'NewMessages' => 'INBOX' === $sFolderName && \MailSo\Config::$CheckNewMessages ?
$this->getFolderNextMessageInformation($sFolderName, $iPrevUidNext, $iUidNext) : array() $this->getFolderNextMessageInformation($sFolderName, $iPrevUidNext, $iUidNext) : array()
); );
@ -679,12 +682,7 @@ class MailClient
*/ */
public function FolderHash(string $sFolderName) : string public function FolderHash(string $sFolderName) : string
{ {
$iCount = 0; list($iCount, $iUnseenCount, $iUidNext, $iHighestModSeq) = $this->initFolderValues($sFolderName);
$iUnseenCount = 0;
$iUidNext = 0;
$iHighestModSeq = 0;
$this->initFolderValues($sFolderName, $iCount, $iUnseenCount, $iUidNext, $iHighestModSeq);
return $this->GenerateFolderHash($sFolderName, $iCount, $iUidNext, $iHighestModSeq); return $this->GenerateFolderHash($sFolderName, $iCount, $iUidNext, $iHighestModSeq);
} }
@ -1555,11 +1553,6 @@ class MailClient
$mAllSortedUids = null; $mAllSortedUids = null;
$mAllThreads = null; $mAllThreads = null;
$iMessageRealCount = 0;
$iMessageUnseenCount = 0;
$iUidNext = 0;
$iHighestModSeq = 0;
$bUseSortIfSupported = $bUseSortIfSupported && $this->oImapClient->IsSupported('SORT'); $bUseSortIfSupported = $bUseSortIfSupported && $this->oImapClient->IsSupported('SORT');
$bUseThreadSortIfSupported = $bUseThreadSortIfSupported ? $bUseThreadSortIfSupported = $bUseThreadSortIfSupported ?
@ -1575,7 +1568,7 @@ class MailClient
$oCacher = null; $oCacher = null;
} }
$this->initFolderValues($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $iUidNext, $iHighestModSeq); list($iMessageRealCount, $iMessageUnseenCount, $iUidNext, $iHighestModSeq) = $this->initFolderValues($sFolderName);
if ($bUseFilter) if ($bUseFilter)
{ {