protected function _parseFetch(Horde_Imap_Client_Interaction_Pipeline $pipeline, $id, Horde_Imap_Client_Tokenize $data)
{
if ($data->next() !== true) {
return;
}
$ob = $pipeline->fetch->get($id);
$ob->setSeq($id);
$flags = $modseq = $uid = false;
while (($tag = $data->next()) !== false) {
$tag = Horde_String::upper($tag);
/* Catch equivalent RFC822 tags, in case server returns them
* (in error, since we only use BODY in FETCH requests). */
switch ($tag) {
case 'RFC822':
$tag = 'BODY[]';
break;
case 'RFC822.HEADER':
$tag = 'BODY[HEADER]';
break;
case 'RFC822.TEXT':
$tag = 'BODY[TEXT]';
break;
}
switch ($tag) {
case 'BODYSTRUCTURE':
$data->next();
$structure = $this->_parseBodystructure($data);
$structure->buildMimeIds();
$ob->setStructure($structure);
break;
case 'ENVELOPE':
$data->next();
$ob->setEnvelope($this->_parseEnvelope($data));
break;
case 'FLAGS':
$data->next();
$ob->setFlags($data->flushIterator());
$flags = true;
break;
case 'INTERNALDATE':
$ob->setImapDate($data->next());
break;
case 'RFC822.SIZE':
$ob->setSize($data->next());
break;
case 'UID':
$ob->setUid($data->next());
$uid = true;
break;
case 'MODSEQ':
$data->next();
$modseq = $data->next();
$data->next();
/* MODSEQ must be greater than 0, so do sanity checking. */
if ($modseq > 0) {
$ob->setModSeq($modseq);
/* Store MODSEQ value. It may be used as the highestmodseq
* once a tagged response is received (RFC 7162 [6]). */
$pipeline->data['modseqs'][] = $modseq;
}
break;
default:
// Catch BODY[*]<#> responses
if (strpos($tag, 'BODY[') === 0) {
// Remove the beginning 'BODY['
$tag = substr($tag, 5);
// BODY[HEADER.FIELDS] request
if (!empty($pipeline->data['fetch_lookup']) && strpos($tag, 'HEADER.FIELDS') !== false) {
$data->next();
$sig = $tag . ' (' . implode(' ', array_map('Horde_String::upper', $data->flushIterator())) . ')';
// Ignore the trailing bracket
$data->next();
$ob->setHeaders($pipeline->data['fetch_lookup'][$sig], $data->next());
} else {
// Remove trailing bracket and octet start info
$tag = substr($tag, 0, strrpos($tag, ']'));
if (!strlen($tag)) {
// BODY[] request
if (!is_null($tmp = $data->nextStream())) {
$ob->setFullMsg($tmp);
}
} elseif (is_numeric(substr($tag, -1))) {
// BODY[MIMEID] request
if (!is_null($tmp = $data->nextStream())) {
$ob->setBodyPart($tag, $tmp);
}
} else {
// BODY[HEADER|TEXT|MIME] request
if (($last_dot = strrpos($tag, '.')) === false) {
$mime_id = 0;
} else {
$mime_id = substr($tag, 0, $last_dot);
$tag = substr($tag, $last_dot + 1);
}
if (!is_null($tmp = $data->nextStream())) {
switch ($tag) {
case 'HEADER':
$ob->setHeaderText($mime_id, $tmp);
break;
case 'TEXT':
$ob->setBodyText($mime_id, $tmp);
break;
case 'MIME':
$ob->setMimeHeader($mime_id, $tmp);
break;
}
}
}
}
} elseif (strpos($tag, 'BINARY[') === 0) {
// Catch BINARY[*]<#> responses
// Remove the beginning 'BINARY[' and the trailing bracket
// and octet start info
$tag = substr($tag, 7, strrpos($tag, ']') - 7);
$body = $data->nextStream();
if (is_null($body)) {
/* Dovecot bug (as of 2.2.12): binary fetch of body
* part may fail with NIL return if decoding failed on
* server. Try again with non-decoded body. */
$bq = $pipeline->data['binaryquery'][$tag];
unset($bq['decode']);
$query = new Horde_Imap_Client_Fetch_Query();
$query->bodyPart($tag, $bq);
$qids = ($quid = $ob->getUid()) ? new Horde_Imap_Client_Ids($quid) : new Horde_Imap_Client_Ids($id, true);
$pipeline->data['fetch_followup'][] = array('_query' => $query, 'ids' => $qids);
} else {
$ob->setBodyPart($tag, $body, empty($this->_temp['literal8']) ? '8bit' : 'binary');
}
} elseif (strpos($tag, 'BINARY.SIZE[') === 0) {
// Catch BINARY.SIZE[*] responses
// Remove the beginning 'BINARY.SIZE[' and the trailing
// bracket and octet start info
$tag = substr($tag, 12, strrpos($tag, ']') - 12);
$ob->setBodyPartSize($tag, $data->next());
}
break;
}
}
/* MODSEQ issue: Oh joy. Per RFC 5162 (see Errata #1807), FETCH FLAGS
* responses are NOT required to provide UID information, even if
* QRESYNC is explicitly enabled. Caveat: the FLAGS information
* returned during a SELECT/EXAMINE MUST contain UIDs so we are OK
* there.
* The good news: all decent IMAP servers (Cyrus, Dovecot) will always
* provide UID information, so this is not normally an issue.
* The bad news: spec-wise, this behavior cannot be 100% guaranteed.
* Compromise: We will watch for a FLAGS response with a MODSEQ and
* check if a UID exists also. If not, put the sequence number in a
* queue - it is possible the UID information may appear later in an
* untagged response. When the command is over, double check to make
* sure there are none of these MODSEQ/FLAGS that are still UID-less.
* In the (rare) event that there is, don't cache anything and
* immediately close the mailbox: flags will be correctly sync'd next
* mailbox open so we only lose a bit of caching efficiency.
* Otherwise, we could end up with an inconsistent cached state.
* This Errata has been fixed in 7162 [3.2.4]. */
if ($flags && $modseq && !$uid) {
$pipeline->data['modseqs_nouid'][] = $id;
}
}