/** * Render the part based on the view mode. * * @param boolean $inline True if viewing inline. * * @return array See parent::render(). */ protected function _IMPrender($inline) { /* RFC 1740 [4]: There are two parts to an appledouble message: * (1) application/applefile * (2) Data embedded in the Mac file * Since the resource fork is not very useful to us, only provide a * means to download. */ /* Display the resource fork download link. */ $iterator = $this->_mimepart->partIterator(); $iterator->rewind(); $mime_id = $iterator->current()->getMimeId(); $iterator->next(); $applefile_id = $iterator->current()->getMimeId(); $id_ob = new Horde_Mime_Id($applefile_id); $data_id = $id_ob->idArithmetic($id_ob::ID_NEXT); $applefile_part = $this->_mimepart[$applefile_id]; $data_part = $this->_mimepart[$data_id]; $data_name = $this->getConfigParam('imp_contents')->getPartName($data_part); $status = new IMP_Mime_Status($this->_mimepart, array(sprintf(_("This message contains a Macintosh file (named \"%s\")."), $data_name), $this->getConfigParam('imp_contents')->linkViewJS($applefile_part, 'download_attach', "Download the Macintosh resource fork."))); $status->icon('mime/apple.png', _("Macintosh File")); /* For inline viewing, attempt to display the data inline. */ $ret = array(); if ($inline && ($disp = $this->getConfigParam('imp_contents')->canDisplay($data_part, IMP_Contents::RENDER_INLINE | IMP_Contents::RENDER_INFO))) { $ret = $this->getConfigParam('imp_contents')->renderMIMEPart($data_id, $disp); } foreach ($iterator as $ob) { $id = $ob->getMimeId(); if (!isset($ret[$id]) && strcmp($id, $data_id) !== 0) { $ret[$id] = strcmp($id, $mime_id) === 0 ? array('data' => '', 'status' => $status, 'type' => 'text/html; charset=' . $this->getConfigParam('charset'), 'wrap' => 'mimePartWrap') : null; } } return $ret; }
/** * @dataProvider idArithmeticProvider */ public function testIdArithmetic($id, $action, $opts, $expected) { $id_ob = new Horde_Mime_Id($id); $this->assertEquals($expected, $id_ob->idArithmetic($action, $opts)); }
/** * Find a MIME type in parent parts. * * @param string $id The MIME ID to begin the search at. * @param string $type The MIME type to search for. * * @return mixed Either the requested MIME part, or null if not found. */ public function findMimeType($id, $type) { $id_ob = new Horde_Mime_Id($id); while (($id_ob->id = $id_ob->idArithmetic($id_ob::ID_UP)) !== null) { if (($part = $this->getMimePart($id_ob->id, array('nocontents' => true))) && $part->getType() == $type) { return $part; } } return null; }
/** * Generates HTML output for 'multipart/signed' MIME parts. * * @return string The HTML output. */ protected function _outputPGPSigned() { global $injector, $prefs, $session; $iterator = $this->_mimepart->partIterator(); $iterator->rewind(); $base_id = $iterator->current()->getMimeId(); $iterator->next(); $signed_id = $iterator->current()->getMimeId(); $id_ob = new Horde_Mime_Id($signed_id); $sig_id = $id_ob->idArithmetic($id_ob::ID_NEXT); if (!IMP_Pgp::enabled()) { return array($sig_id => null); } $status = new IMP_Mime_Status($this->_mimepart); $status->addText(_("The data in this part has been digitally signed via PGP.")); $status->icon('mime/encryption.png', 'PGP'); $ret = array($base_id => array('data' => '', 'nosummary' => true, 'status' => array($status), 'type' => 'text/html; charset=' . $this->getConfigParam('charset'), 'wrap' => 'mimePartWrap'), $sig_id => null); if ($prefs->getValue('pgp_verify') || $injector->getInstance('Horde_Variables')->pgp_verify_msg) { $imp_contents = $this->getConfigParam('imp_contents'); $sig_part = $imp_contents->getMimePart($sig_id); $status2 = new IMP_Mime_Status($this->_mimepart); if (!$sig_part) { $status2->action(IMP_Mime_Status::ERROR); $sig_text = _("This digitally signed message is broken."); $ret[$base_id]['wrap'] = 'mimePartWrapInvalid'; } else { /* Close session, since this may be a long-running * operation. */ $session->close(); try { $imp_pgp = $injector->getInstance('IMP_Pgp'); if ($sig_raw = $sig_part->getMetadata(Horde_Crypt_Pgp_Parse::SIG_RAW)) { $sig_result = $imp_pgp->verifySignature($sig_raw, $this->_getSender()->bare_address, null, $sig_part->getMetadata(Horde_Crypt_Pgp_Parse::SIG_CHARSET)); } else { $stream = $imp_contents->isEmbedded($signed_id) ? $this->_mimepart->getMetadata(self::PGP_SIGN_ENC) : $imp_contents->getBodyPart($signed_id, array('mimeheaders' => true, 'stream' => true))->data; rewind($stream); stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol'); $filter = stream_filter_append($stream, 'horde_eol', STREAM_FILTER_READ, array('eol' => Horde_Mime_Part::RFC_EOL)); $sig_result = $imp_pgp->verifySignature(stream_get_contents($stream), $this->_getSender()->bare_address, $sig_part->getContents()); stream_filter_remove($filter); } $status2->action(IMP_Mime_Status::SUCCESS); $sig_text = $sig_result->message; $ret[$base_id]['wrap'] = 'mimePartWrapValid'; } catch (Horde_Exception $e) { $status2->action(IMP_Mime_Status::ERROR); $sig_text = $e->getMessage(); $ret[$base_id]['wrap'] = 'mimePartWrapInvalid'; } } $status2->addText($this->_textFilter($sig_text, 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::NOHTML))); $ret[$base_id]['status'][] = $status2; } else { $status->addMimeAction('pgpVerifyMsg', _("Click to verify the message.")); } return $ret; }
/** * @deprecated Use Horde_Mime_Id instead. */ public static function mimeIdArithmetic($id, $action, $options = array()) { $id_ob = new Horde_Mime_Id($id); switch ($action) { case 'down': $action = $id_ob::ID_DOWN; break; case 'next': $action = $id_ob::ID_NEXT; break; case 'prev': $action = $id_ob::ID_PREV; break; case 'up': $action = $id_ob::ID_UP; break; } return $id_ob->idArithmetic($action, $options); }
/** * Return the rendered information about the Horde_Mime_Part object. * * @return array See parent::render(). */ protected function _renderInfo() { $imp_contents = $this->getConfigParam('imp_contents'); $machine = $original = null; $ret = array(); switch ($this->_mimepart->getType()) { case 'message/disposition-notification': /* Outlook can send a disposition-notification without the * RFC-required multipart/report wrapper. */ $machine = $imp_contents->getMimePart($this->_mimepart->getMimeId()); break; case 'multipart/report': /* RFC 3798 [3]: There are three parts to a delivery status * multipart/report message: * (1) Human readable message * (2) Machine parsable body part * [message/disposition-notification] * (3) Original message (optional) */ $iterator = $this->_mimepart->partIterator(false); $iterator->rewind(); if (!($curr = $iterator->current())) { break; } $part1_id = $curr->getMimeId(); $id_ob = new Horde_Mime_Id($part1_id); /* Technical details. */ $id_ob->id = $id_ob->idArithmetic($id_ob::ID_NEXT); $ret[$id_ob->id] = null; $machine = $imp_contents->getMimePart($id_ob->id); /* Original sent message. */ $original = $imp_contents->getMimePart($id_ob->idArithmetic($id_ob::ID_NEXT)); if ($original) { foreach ($this->_mimepart->partIterator() as $val) { $ret[$val->getMimeId()] = null; } /* Allow the human readable part to be displayed * separately. */ unset($ret[$part1_id]); } break; default: return array($this->_mimepart->getMimeId() => null); } $mdn_status = array(_("A message you have sent has resulted in a return notification from the recipient.")); if ($machine) { $parse = Horde_Mime_Headers::parseHeaders(preg_replace('/\\n{2,}/', "\n", strtr($machine->getContents(), "\r", "\n"))); if (isset($parse['Final-Recipient'])) { list(, $recip) = explode(';', $parse['Final-Recipient']->value_single); if ($recip) { $mdn_status[] = sprintf(_("Recipient: %s"), trim($recip)); } } if (isset($parse['Disposition'])) { list($modes, $type) = explode(';', $parse['Disposition']->value_single); list($action, $sent) = explode('/', $modes); switch (trim(Horde_String::lower($type))) { case 'displayed': $mdn_status[] = _("The message has been displayed to the recipient."); break; case 'deleted': $mdn_status[] = _("The message has been deleted by the recipient; it is unknown whether they viewed the message."); break; } switch (trim(Horde_String::lower($action))) { case 'manual-action': // NOOP break; case 'automatic-action': // NOOP break; } switch (trim(Horde_String::lower($sent))) { case 'mdn-sent-manually': $mdn_status[] = _("This notification was explicitly sent by the recipient."); break; case 'mdn-sent-automatically': // NOOP break; } } } $status = new IMP_Mime_Status($this->_mimepart, $mdn_status); $status->icon('info_icon.png', _("Info")); if ($original) { $status->addText($imp_contents->linkViewJS($original, 'view_attach', _("View the text of the original sent message."), array('params' => array('ctype' => 'message/rfc822', 'mode' => IMP_Contents::RENDER_FULL)))); } $ret[$this->_mimepart->getMimeId()] = array('data' => '', 'status' => $status, 'type' => 'text/html; charset=' . $this->getConfigParam('charset'), 'wrap' => 'mimePartWrap'); return $ret; }
/** * Parse signed data. * * @param boolean $sig_only Only do signature checking? * * @return mixed See self::_getEmbeddedMimeParts(). */ protected function _parseSignedData($sig_only = false) { $iterator = $this->_mimepart->partIterator(); $iterator->rewind(); if (!($curr = $iterator->current())) { return null; } $base_id = $curr->getMimeId(); $iterator->next(); if (!($curr = $iterator->current())) { // application/pkcs-7-mime might be the base part. // See RFC 5751 3.4.2 $data_id = $base_id; } else { $data_id = $curr->getMimeId(); } $id_ob = new Horde_Mime_Id($data_id); $sig_id = $id_ob->idArithmetic($id_ob::ID_NEXT); /* Initialize inline data. */ $status = new IMP_Mime_Status($this->_mimepart, _("The data in this part has been digitally signed via S/MIME.")); $status->icon('mime/encryption.png', 'S/MIME'); $cache = $this->getConfigParam('imp_contents')->getViewCache(); $cache->smime[$base_id] = array('sig' => $sig_id, 'status' => $status, 'wrap' => 'mimePartWrap'); if (!IMP_Smime::enabled()) { $status->addText(_("S/MIME support is not enabled so the digital signature is unable to be verified.")); return null; } $imp_contents = $this->getConfigParam('imp_contents'); $stream = $imp_contents->isEmbedded($base_id) ? $this->_mimepart->getMetadata('imp-smime-decrypt')->stream : $this->_getPartStream($base_id); $raw_text = $this->_mimepart->replaceEOL($stream, Horde_Mime_Part::RFC_EOL); $this->_initSmime(); $sig_result = null; if ($GLOBALS['prefs']->getValue('smime_verify') || $GLOBALS['injector']->getInstance('Horde_Variables')->smime_verify_msg) { try { $sig_result = $this->_impsmime->verifySignature($raw_text); if ($sig_result->verify) { $status->action(IMP_Mime_Status::SUCCESS); } else { $status->action(IMP_Mime_Status::WARNING); } if (!is_array($sig_result->email)) { $sig_result->email = array($sig_result->email); } $email = implode(', ', $sig_result->email); $cache->smime[$base_id]['wrap'] = 'mimePartWrapValid'; $status->addText($sig_result->msg); if (!empty($sig_result->cert)) { $cert = $this->_impsmime->parseCert($sig_result->cert); if (isset($cert['certificate']['subject']['CommonName']) && strcasecmp($email, $cert['certificate']['subject']['CommonName']) !== 0) { $email = $cert['certificate']['subject']['CommonName'] . ' (' . trim($email) . ')'; } } if (!empty($sig_result->cert) && isset($sig_result->email) && $GLOBALS['registry']->hasMethod('contacts/addField') && $GLOBALS['prefs']->getValue('add_source')) { $status->addText(sprintf(_("Sender: %s"), $imp_contents->linkViewJS($this->_mimepart, 'view_attach', htmlspecialchars($email), array('jstext' => _("View certificate details"), 'params' => array('mode' => IMP_Contents::RENDER_INLINE, 'view_smime_key' => 1))))); foreach ($sig_result->email as $single_email) { try { $this->_impsmime->getPublicKey($single_email); } catch (Horde_Exception $e) { $imple = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create('IMP_Ajax_Imple_ImportEncryptKey', array('mime_id' => $base_id, 'muid' => strval($imp_contents->getIndicesOb()), 'type' => 'smime')); $status->addText(Horde::link('#', '', '', '', '', '', '', array('id' => $imple->getDomId())) . _("Save the certificate to your Address Book.") . '</a>'); break; } } } elseif (strlen($email)) { $status->addText(sprintf(_("Sender: %s"), htmlspecialchars($email))); } } catch (Horde_Exception $e) { $status->action(IMP_Mime_Status::ERROR); $cache->smime[$base_id]['wrap'] = 'mimePartWrapInvalid'; $status->addText($e->getMessage()); } } else { $status->addMimeAction('smimeVerifyMsg', _("Click to verify the data.")); } if ($sig_only) { return; } if (!($subpart = $imp_contents->getMimePart($sig_id))) { try { $msg_data = $this->_impsmime->extractSignedContents($raw_text); $subpart = Horde_Mime_Part::parseMessage($msg_data, array('forcemime' => true)); } catch (Horde_Exception $e) { $status->addText($e->getMessage()); return null; } } return $subpart; }
/** * Return the rendered information about the Horde_Mime_Part object. * * @return array See parent::render(). */ protected function _renderInfo() { $imp_contents = $this->getConfigParam('imp_contents'); $machine = $original = $status = null; $mime_id = $this->_mimepart->getMimeId(); $ret = array(); switch ($this->_mimepart->getType()) { case 'message/delivery-status': $machine = $imp_contents->getMimePart($mime_id); break; case 'multipart/report': /* RFC 3464 [2]: There are three parts to a delivery status * multipart/report message: * (1) Human readable message * (2) Machine parsable body part (message/delivery-status) * (3) Returned message (optional) */ $iterator = $this->_mimepart->partIterator(false); $iterator->rewind(); if (!($curr = $iterator->current())) { break; } $part1_id = $curr->getMimeId(); $id_ob = new Horde_Mime_Id($part1_id); /* Technical details. */ $id_ob->id = $id_ob->idArithmetic($id_ob::ID_NEXT); $ret[$id_ob->id] = null; $machine = $imp_contents->getMimePart($id_ob->id); /* Returned message. */ $original = $imp_contents->getMimePart($id_ob->idArithmetic($id_ob::ID_NEXT)); if ($original) { foreach ($this->_mimepart->partIterator() as $val) { $ret[$val->getMimeId()] = null; } /* Allow the human readable part to be displayed * separately. */ unset($ret[$part1_id]); } break; } if (!$machine) { return array($mime_id => null); } $parse = Horde_Mime_Headers::parseHeaders(preg_replace('/\\n{2,}/', "\n", strtr($machine->getContents(), "\r", "\n"))); /* Information on the message status is found in the 'Action' * field located in part #2 (RFC 3464 [2.3.3]). */ if (isset($parse['Action'])) { switch (trim($parse['Action']->value_single)) { case 'failed': case 'delayed': $msg_link = _("View details of the returned message."); $status_action = IMP_Mime_Status::ERROR; $status_msg = _("ERROR: Your message could not be delivered."); break; case 'delivered': case 'expanded': case 'relayed': $msg_link = _("View details of the delivered message."); $status_action = IMP_Mime_Status::SUCCESS; $status_msg = _("Your message was successfully delivered."); break; } if (isset($msg_link)) { $status = new IMP_Mime_Status($this->_mimepart, $status_msg); $status->action($status_action); if (isset($parse['Final-Recipient'])) { list(, $recip) = explode(';', $parse['Final-Recipient']->value_single); $recip_ob = new Horde_Mail_Rfc822_List($recip); if (count($recip_ob)) { $status->addText(sprintf(_("Recipient: %s"), $recip_ob[0])); } } /* Display a link to the returned message, if it exists. */ if ($original) { $status->addText($imp_contents->linkViewJS($original, 'view_attach', $msg_link, array('params' => array('ctype' => 'message/rfc822')))); } } } $ret[$mime_id] = array_filter(array('data' => '', 'status' => $status ?: null, 'type' => 'text/html; charset=' . $this->getConfigParam('charset'), 'wrap' => 'mimePartWrap')); return $ret; }
/** * Render out the currently set contents. * * @param boolean $inline Are we viewing inline? * * @return array See parent::render(). */ protected function _IMPrender($inline) { $base_id = $this->_mimepart->getMimeId(); $display_ids = $ret = array(); $prefer_plain = $GLOBALS['prefs']->getValue('alternative_display') == 'text'; /* Look for a displayable part. RFC: show the LAST choice that can be * displayed inline. If an alternative is itself a multipart, the user * agent is allowed to show that alternative, an earlier alternative, * or both. If we find a multipart alternative that contains at least * one viewable part, we will display all viewable subparts of that * alternative. */ $imp_contents = $this->getConfigParam('imp_contents'); foreach ($this->_mimepart->partIterator() as $val) { $id = $val->getMimeId(); $ret[$id] = null; if (strcmp($base_id, $id) !== 0 && $imp_contents->canDisplay($id, $inline ? IMP_Contents::RENDER_INLINE : IMP_Contents::RENDER_FULL) && (!$prefer_plain || $val->getType() != 'text/html' && $val->getPrimaryType() == 'text')) { $display_ids[strval($id)] = true; } } /* If we found no IDs, return now. */ if (empty($display_ids)) { $ret[$base_id] = array('data' => '', 'status' => new IMP_Mime_Status($this->_mimepart, _("There are no alternative parts that can be displayed inline.")), 'type' => 'text/html; charset=' . $this->getConfigParam('charset')); return $ret; } /* If the last viewable message exists in a subpart, back up to the * base multipart and display all viewable parts in that multipart. * Else, display the single part. */ end($display_ids); $curr_id = key($display_ids); while (!is_null($curr_id) && strcmp($base_id, $curr_id) !== 0) { if (array_key_exists($curr_id, $ret)) { $disp_id = $curr_id; } $id_ob = new Horde_Mime_Id($curr_id); $curr_id = $id_ob->idArithmetic($id_ob::ID_UP); } /* At this point, $ret contains stubs for all parts living in the base * alternative part. * Go through all subparts of displayable part and make sure all parts * are rendered. Parts not rendered will be marked as not being * handled by this viewer (Bug #9365). */ $render_part = $this->_mimepart[$disp_id]; foreach ($render_part->partIterator() as $val) { $id = $val->getMimeId(); $need_render[$id] = $subparts[$id] = true; } /* Track whether there is at least one viewable (non-empty) part. */ $viewable = false; $viewable_ret = $ret; foreach (array_keys($subparts) as $val) { if (isset($display_ids[$val]) && isset($need_render[$val])) { $render = $this->getConfigParam('imp_contents')->renderMIMEPart($val, $inline ? IMP_Contents::RENDER_INLINE : IMP_Contents::RENDER_FULL); foreach (array_keys($render) as $id) { unset($need_render[$id]); if (!$inline) { if (!is_null($render[$id])) { return array($base_id => $render[$id]); } } else { $ret[$id] = $render[$id]; if (!is_null($ret[$id])) { $viewable = true; } } } } } if (!$inline) { return null; } unset($need_render[$disp_id]); foreach (array_keys($need_render) as $val) { unset($ret[$val]); } /* If we reach this point, and have at least one subpart with no * viewable parts, check to see there is not a richer, non-inline * viewable part that exists in the message. */ if (!$viewable) { $id_ob = new Horde_Mime_Id($disp_id); if (array_key_exists($id_ob->idArithmetic($id_ob::ID_NEXT), $viewable_ret)) { $ret[$disp_id] = array('data' => '', 'status' => new IMP_Mime_Status($this->_mimepart, array(_("This part contains no message contents."), _("There are no alternative parts that can be displayed inline."))), 'type' => 'text/html; charset=' . $this->getConfigParam('charset')); } } return $ret; }