/** * If this MIME part can contain embedded MIME part(s), and those part(s) * exist, return a representation of that data. * * @return mixed A Horde_Mime_Part object representing the embedded data. * Returns null if no embedded MIME part(s) exist. */ protected function _getEmbeddedMimeParts() { /* Get the data from the attachment. */ try { if (!($tnef = $this->getConfigParam('tnef'))) { $tnef = Horde_Compress::factory('Tnef'); $this->setConfigParam('tnef', $tnef); } $tnefData = $tnef->decompress($this->_mimepart->getContents()); } catch (Horde_Compress_Exception $e) { $tnefData = array(); } if (!count($tnefData)) { return null; } $mixed = new Horde_Mime_Part(); $mixed->setType('multipart/mixed'); reset($tnefData); while (list(, $data) = each($tnefData)) { $temp_part = new Horde_Mime_Part(); $temp_part->setName($data['name']); $temp_part->setDescription($data['name']); $temp_part->setContents($data['stream']); /* Short-circuit MIME-type guessing for winmail.dat parts; * we're showing enough entries for them already. */ $type = $data['type'] . '/' . $data['subtype']; if (in_array($type, array('application/octet-stream', 'application/base64'))) { $type = Horde_Mime_Magic::filenameToMIME($data['name']); } $temp_part->setType($type); $mixed->addPart($temp_part); } return $mixed; }
/** */ public function report(IMP_Contents $contents, $action) { global $injector, $registry; $imp_compose = $injector->getInstance('IMP_Factory_Compose')->create(); switch ($this->_format) { case 'redirect': /* Send the message. */ try { $imp_compose->redirectMessage($contents->getIndicesOb()); $imp_compose->sendRedirectMessage($this->_email, false); return true; } catch (IMP_Compose_Exception $e) { $e->log(); } break; case 'digest': default: try { $from_line = $injector->getInstance('IMP_Identity')->getFromLine(); } catch (Horde_Exception $e) { $from_line = null; } /* Build the MIME structure. */ $mime = new Horde_Mime_Part(); $mime->setType('multipart/digest'); $rfc822 = new Horde_Mime_Part(); $rfc822->setType('message/rfc822'); $rfc822->setContents($contents->fullMessageText(array('stream' => true))); $mime->addPart($rfc822); $spam_headers = new Horde_Mime_Headers(); $spam_headers->addMessageIdHeader(); $spam_headers->addHeader('Date', date('r')); $spam_headers->addHeader('To', $this->_email); if (!is_null($from_line)) { $spam_headers->addHeader('From', $from_line); } $spam_headers->addHeader('Subject', sprintf(_("%s report from %s"), $action == IMP_Spam::SPAM ? 'spam' : 'innocent', $registry->getAuth())); /* Send the message. */ try { $recip_list = $imp_compose->recipientList(array('to' => $this->_email)); $imp_compose->sendMessage($recip_list['list'], $spam_headers, $mime, 'UTF-8'); $rfc822->clearContents(); return true; } catch (IMP_Compose_Exception $e) { $e->log(); $rfc822->clearContents(); } break; } return false; }
/** * Append the current Draft message to the IMAP server. * * @return array An array with the following keys: * - uid: (integer) The new draft message's IMAP UID. * - atchash: (array) An attachment hash of newly added attachments. */ public function append($folderid) { // Init $atc_map = array(); $atc_hash = array(); // Create the wrapper part. $base = new Horde_Mime_Part(); $base->setType('multipart/mixed'); // Check to see if we have any existing parts to add. if (!empty($this->_imapMessage)) { foreach ($this->_imapMessage->getStructure() as $part) { if ($part->isAttachment() && !in_array($part->getMimeId(), $this->_atcDelete)) { $base->addPart($this->_imapMessage->getMimePart($part->getMimeId())); } } } // Add body $base->addPart($this->_textPart); // Add Mime headers $base->addMimeHeaders(array('headers' => $this->_headers)); foreach ($this->_atcAdd as $atc) { $base->addPart($atc); $atc_map[$atc->displayname] = $atc->clientid; } $stream = $base->toString(array('stream' => true, 'headers' => $this->_headers->toString())); $new_uid = $this->_imap->appendMessage($folderid, $stream, array('\\draft', '\\seen')); foreach ($base as $part) { if ($part->isAttachment() && !empty($atc_map[$part->getName()])) { $atc_hash['add'][$atc_map[$part->getName()]] = $folderid . ':' . $stat['id'] . ':' . $part->getMimeId(); } } // If we pulled down an existing Draft, delete it now since the // new one will replace it. if (!empty($this->_imapMessage)) { $this->_imap->deleteMessages(array($this->_draftUid), $folderid); } return array('uid' => $new_uid, 'atchash' => $atc_hash); }
/** * Sends out iTip task notification to the assignee. * * Can be used to send task invitations, updates, and cancellations. * * @param Nag_Task $task The task in question. * @param Horde_Notification_Handler $notification * A notification object used to show result status. * @param integer $action * The type of notification to send. One of the Nag::ITIP_* values. * @param Horde_Date $instance * If cancelling a single instance of a recurring task, the date of * this instance. * @param string $range The range parameter if this is a recurring event. * Possible values are self::RANGE_THISANDFUTURE */ public static function sendITipNotifications(Nag_Task $task, Horde_Notification_Handler $notification, $action, Horde_Date $instance = null, $range = null) { global $injector, $registry, $nag_shares; if (!$task->assignee) { return; } $ident = $injector->getInstance('Horde_Core_Factory_Identity')->create($task->creator); if (!$ident->getValue('from_addr')) { $notification->push(sprintf(_("You do not have an email address configured in your Personal Information Preferences. You must set one %shere%s before event notifications can be sent."), $registry->getServiceLink('prefs', 'kronolith')->add(array('app' => 'horde', 'group' => 'identities'))->link(), '</a>'), 'horde.error', array('content.raw')); return; } // Generate image mime part first and only once, because we // need the Content-ID. $image = self::getImagePart('big_invitation.png'); $share = $nag_shares->getShare($task->tasklist); $view = new Horde_View(array('templatePath' => NAG_TEMPLATES . '/itip')); new Horde_View_Helper_Text($view); $view->identity = $ident; $view->task = $task; $view->imageId = $image->getContentId(); $email = Nag::getUserEmail($task->assignee); if (strpos($email, '@') === false) { continue; } /* Determine all notification-specific strings. */ $method = 'REQUEST'; switch ($action) { case self::ITIP_CANCEL: /* Cancellation. */ $method = 'CANCEL'; $filename = 'task-cancellation.ics'; $view->subject = sprintf(_("Cancelled: %s"), $task->name); if (empty($instance)) { $view->header = sprintf(_("%s has cancelled \"%s\"."), $ident->getName(), $task->name); } else { $view->header = sprintf(_("%s has cancelled an instance of the recurring \"%s\"."), $ident->getName(), $task->name); } break; case self::ITIP_UPDATE: if (!empty($task->organizer) && $task->organizer != Nag::getUserEmail($task->creator)) { // Sending a progress update. $method = 'REPLY'; } else { $method = 'UPDATE'; } case self::ITIP_REQUEST: default: if (empty($task->status) || $task->status == self::RESPONSE_NONE) { /* Invitation. */ $filename = 'task-invitation.ics'; $view->subject = $task->name; $view->header = sprintf(_("%s wishes to make you aware of \"%s\"."), $ident->getName(), $task->name); } else { $filename = 'task-update.ics'; $view->subject = sprintf(_("Updated: %s."), $task->name); $view->header = sprintf(_("%s wants to notify you about changes of \"%s\"."), $ident->getName(), $task->name); } break; } $view->attendees = $email; $view->organizer = empty($task->organizer) ? $registry->convertUserName($task->creator, false) : $task->organizer; /* Build the iCalendar data */ $iCal = new Horde_Icalendar(); $iCal->setAttribute('METHOD', $method); $vevent = $task->toiCalendar($iCal); $iCal->addComponent($vevent); /* text/calendar part */ $ics = new Horde_Mime_Part(); $ics->setType('text/calendar'); $ics->setContents($iCal->exportvCalendar()); $ics->setName($filename); $ics->setContentTypeParameter('METHOD', $method); $ics->setCharset('UTF-8'); $ics->setEOL("\r\n"); /* application/ics part */ $ics2 = clone $ics; $ics2->setType('application/ics'); /* multipart/mixed part */ $multipart = new Horde_Mime_Part(); $multipart->setType('multipart/mixed'); $inner = self::buildMimeMessage($view, 'notification', $image); $inner->addPart($ics); $multipart->addPart($inner); $multipart->addPart($ics2); $recipient = $method != 'REPLY' ? new Horde_Mail_Rfc822_Address($email) : new Horde_Mail_Rfc822_Address($task->organizer); $mail = new Horde_Mime_Mail(array('Subject' => $view->subject, 'To' => $recipient, 'From' => $ident->getDefaultFromAddress(true), 'User-Agent' => 'Nag ' . $registry->getVersion())); $mail->setBasePart($multipart); try { $mail->send($injector->getInstance('Horde_Mail')); $notification->push(sprintf(_("The task request notification to %s was successfully sent."), $recipient), 'horde.success'); } catch (Horde_Mime_Exception $e) { $notification->push(sprintf(_("There was an error sending a task request notification to %s: %s"), $recipient, $e->getMessage(), $e->getCode()), 'horde.error'); } }
/** * Encrypts a MIME part using PGP. * * @param Horde_Mime_Part $mime_part The object to encrypt. * @param array $params The parameters required for * encryption * ({@see _encryptMessage()}). * * @return mixed A Horde_Mime_Part object that is encrypted according to * RFC 3156. * @throws Horde_Crypt_Exception */ public function encryptMIMEPart($mime_part, $params = array()) { $params = array_merge($params, array('type' => 'message')); $signenc_body = $mime_part->toString(array('canonical' => true, 'headers' => true)); $message_encrypt = $this->encrypt($signenc_body, $params); /* Set up MIME Structure according to RFC 3156. */ $part = new Horde_Mime_Part(); $part->setType('multipart/encrypted'); $part->setHeaderCharset('UTF-8'); $part->setContentTypeParameter('protocol', 'application/pgp-encrypted'); $part->setDescription(Horde_Crypt_Translation::t("PGP Encrypted Data")); $part->setContents("This message is in MIME format and has been PGP encrypted.\n"); $part1 = new Horde_Mime_Part(); $part1->setType('application/pgp-encrypted'); $part1->setCharset(null); $part1->setContents("Version: 1\n", array('encoding' => '7bit')); $part->addPart($part1); $part2 = new Horde_Mime_Part(); $part2->setType('application/octet-stream'); $part2->setCharset(null); $part2->setContents($message_encrypt, array('encoding' => '7bit')); $part2->setDisposition('inline'); $part->addPart($part2); return $part; }
/** * Return the response as a MIME message. * * @param Horde_Itip_Response_Type $type The response type. * @param Horde_Itip_Response_Options $options The options for the response. * * @return array A list of two object: The mime headers and the mime * message. */ public function getMultiPartMessage(Horde_Itip_Response_Type $type, Horde_Itip_Response_Options $options) { $message = new Horde_Mime_Part(); $message->setType('multipart/alternative'); list($headers, $ics) = $this->getMessage($type, $options); $body = new Horde_Mime_Part(); $body->setType('text/plain'); $options->prepareMessageMimePart($body); $body->setContents(Horde_String::wrap($type->getMessage(), 76)); $message->addPart($body); $message->addPart($ics); return array($headers, $message); }
<?php $part1 = new Horde_Mime_Part(); $part1->setType('text/plain'); $part1->setTransferEncoding('quoted-printable'); $part1->setCharset('UTF-8'); $part1->setDisposition('inline'); $part1->setBytes(249); $part2 = new Horde_Mime_Part(); $part2->setType('application/x-vnd.kolab.note'); $part2->setTransferEncoding('quoted-printable'); $part2->setName('kolab.xml'); $part2->setDisposition('attachment'); $part2->setBytes(704); $message = new Horde_Mime_Part(); $message->setType('multipart/mixed'); $message->addPart($part1); $message->addPart($part2); $message->buildMimeIds(0); return $message;
/** * Recursively parse BODYSTRUCTURE data from a FETCH return (see * RFC 3501 [7.4.2]). * * @param array $data The tokenized information from the server. * * @return array The array of bodystructure information. */ protected function _parseStructure($data) { $ob = new Horde_Mime_Part(); // If index 0 is an array, this is a multipart part. if (is_array($data[0])) { // Keep going through array values until we find a non-array. for ($i = 0, $cnt = count($data); $i < $cnt; ++$i) { if (!is_array($data[$i])) { break; } $ob->addPart($this->_parseStructure($data[$i])); } // The first string entry after an array entry gives us the // subpart type. $ob->setType('multipart/' . $data[$i]); // After the subtype is further extension information. This // information MAY not appear for BODYSTRUCTURE requests. // This is parameter information. if (isset($data[++$i]) && is_array($data[$i])) { foreach ($this->_parseStructureParams($data[$i], 'content-type') as $key => $val) { $ob->setContentTypeParameter($key, $val); } } // This is disposition information. if (isset($data[++$i]) && is_array($data[$i])) { $ob->setDisposition($data[$i][0]); foreach ($this->_parseStructureParams($data[$i][1], 'content-disposition') as $key => $val) { $ob->setDispositionParameter($key, $val); } } // This is language information. It is either a single value or // a list of values. if (isset($data[++$i])) { $ob->setLanguage($data[$i]); } // Ignore: location (RFC 2557) // There can be further information returned in the future, but // for now we are done. } else { $ob->setType($data[0] . '/' . $data[1]); foreach ($this->_parseStructureParams($data[2], 'content-type') as $key => $val) { $ob->setContentTypeParameter($key, $val); } if ($data[3] !== null) { $ob->setContentId($data[3]); } if ($data[4] !== null) { $ob->setDescription(Horde_Mime::decode($data[4])); } if ($data[5] !== null) { $ob->setTransferEncoding($data[5]); } if ($data[6] !== null) { $ob->setBytes($data[6]); } // If the type is 'message/rfc822' or 'text/*', several extra // fields are included switch ($ob->getPrimaryType()) { case 'message': if ($ob->getSubType() == 'rfc822') { // Ignore: envelope $ob->addPart($this->_parseStructure($data[8])); // Ignore: lines $i = 10; } else { $i = 7; } break; case 'text': // Ignore: lines $i = 8; break; default: $i = 7; break; } // After the subtype is further extension information. This // information MAY appear for BODYSTRUCTURE requests. // Ignore: MD5 // This is disposition information if (isset($data[++$i]) && is_array($data[$i])) { $ob->setDisposition($data[$i][0]); foreach ($this->_parseStructureParams($data[$i][1], 'content-disposition') as $key => $val) { $ob->setDispositionParameter($key, $val); } } // This is language information. It is either a single value or // a list of values. if (isset($data[++$i])) { $ob->setLanguage($data[$i]); } // Ignore: location (RFC 2557) } return $ob; }
/** * Builds a proper AS mail message object. * * @param Horde_Imap_Client_Mailbox $mbox The IMAP mailbox. * @param Horde_Imap_Client_Data_Fetch $data The fetch results. * @param array $options Additional Options: * - truncation: (integer) Truncate the message body to this length. * DEFAULT: No truncation. * - bodyprefs: (array) Bodyprefs, if sent from device. * DEFAULT: none (No body prefs sent or enforced). * - bodypartprefs: (array) Bodypartprefs, if sent from device. * DEFAULT: none (No body part prefs sent or enforced). * - mimesupport: (integer) Indicates if MIME is supported or not. * Possible values: 0 - Not supported 1 - Only S/MIME or * 2 - All MIME. * DEFAULT: 0 (No MIME support) * - protocolversion: (float) The EAS protocol version to support. * DEFAULT: 2.5 * * @return Horde_ActiveSync_Message_Mail The message object suitable for * streaming to the device. */ protected function _buildMailMessage(Horde_Imap_Client_Mailbox $mbox, Horde_Imap_Client_Data_Fetch $data, $options = array()) { $version = empty($options['protocolversion']) ? Horde_ActiveSync::VERSION_TWOFIVE : $options['protocolversion']; $imap_message = new Horde_ActiveSync_Imap_Message($this->_getImapOb(), $mbox, $data); $eas_message = Horde_ActiveSync::messageFactory('Mail'); // Build To: data (POOMMAIL_TO has a max length of 32768). $to = $imap_message->getToAddresses(); $eas_message->to = array_pop($to['to']); foreach ($to['to'] as $to_atom) { if (strlen($eas_message->to) + strlen($to_atom) > 32768) { break; } $eas_message->to .= ',' . $to_atom; } $eas_message->displayto = implode(';', $to['displayto']); if (empty($eas_message->displayto)) { $eas_message->displayto = $eas_message->to; } // Ensure we don't send broken UTF8 data to the client. It makes clients // angry. And we don't like angry clients. $hdr_charset = $imap_message->getStructure()->getHeaderCharset(); // Fill in other header data try { $eas_message->from = $imap_message->getFromAddress(); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } try { $eas_message->cc = $imap_message->getCc(); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } try { $eas_message->reply_to = $imap_message->getReplyTo(); } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } $eas_message->subject = Horde_ActiveSync_Utils::ensureUtf8($imap_message->getSubject(), $hdr_charset); $eas_message->threadtopic = $eas_message->subject; $eas_message->datereceived = $imap_message->getDate(); $eas_message->read = $imap_message->getFlag(Horde_Imap_Client::FLAG_SEEN); // Default to IPM.Note - may change below depending on message content. $eas_message->messageclass = 'IPM.Note'; // Codepage id. MS recommends to always set to UTF-8 when possible. // See http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756%28v=vs.85%29.aspx $eas_message->cpid = Horde_ActiveSync_Message_Mail::INTERNET_CPID_UTF8; // Message importance. First try X-Priority, then Importance since // Outlook sends the later. if ($priority = $imap_message->getHeaders()->getValue('X-priority')) { $priority = preg_replace('/\\D+/', '', $priority); } else { $priority = $imap_message->getHeaders()->getValue('Importance'); } $eas_message->importance = $this->_getEASImportance($priority); // Get the body data. $mbd = $imap_message->getMessageBodyDataObject($options); if ($version == Horde_ActiveSync::VERSION_TWOFIVE) { $eas_message->body = $mbd->plain['body']->stream; $eas_message->bodysize = $mbd->plain['body']->length(true); $eas_message->bodytruncated = $mbd->plain['truncated']; $eas_message->attachments = $imap_message->getAttachments($version); } else { // Get the message body and determine original type. if ($mbd->html) { $eas_message->airsyncbasenativebodytype = Horde_ActiveSync::BODYPREF_TYPE_HTML; } else { $eas_message->airsyncbasenativebodytype = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; } $airsync_body = Horde_ActiveSync::messageFactory('AirSyncBaseBody'); $body_type_pref = $mbd->getBodyTypePreference(); if ($body_type_pref == Horde_ActiveSync::BODYPREF_TYPE_MIME) { $this->_logger->info(sprintf('[%s] Sending MIME Message.', $this->_procid)); // ActiveSync *REQUIRES* all data sent to be in UTF-8, so we // must convert the body parts to UTF-8. Unfortunately if the // email is signed (or encrypted for that matter) we can't // alter the data in anyway or the signature will not be // verified, so we fetch the entire message and hope for the best. if (!$imap_message->isSigned() && !$imap_message->isEncrypted()) { $mime = new Horde_Mime_Part(); if ($mbd->plain) { $plain_mime = new Horde_Mime_Part(); $plain_mime->setType('text/plain'); $plain_mime->setContents($mbd->plain['body']->stream, array('usestream' => true)); $plain_mime->setCharset('UTF-8'); } if ($mbd->html) { $html_mime = new Horde_Mime_Part(); $html_mime->setType('text/html'); $html_mime->setContents($mbd->html['body']->stream, array('usestream' => true)); $html_mime->setCharset('UTF-8'); } // Sanity check the mime type if (!$mbd->html && !empty($plain_mime)) { $mime = $plain_mime; } elseif (!$mbd->plain && !empty($html_mime)) { $mime = $html_mime; } elseif (!empty($plain_mime) && !empty($html_mime)) { $mime->setType('multipart/alternative'); $mime->addPart($plain_mime); $mime->addPart($html_mime); } $html_mime = null; $plain_mime = null; // If we have attachments, create a multipart/mixed wrapper. if ($imap_message->hasAttachments()) { $base = new Horde_Mime_Part(); $base->setType('multipart/mixed'); $base->addPart($mime); $atc = $imap_message->getAttachmentsMimeParts(); foreach ($atc as $atc_part) { $base->addPart($atc_part); } $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } else { $base = $mime; } $mime = null; // Populate the EAS body structure with the MIME data, but // remove the Content-Type and Content-Transfer-Encoding // headers since we are building this ourselves. $headers = $imap_message->getHeaders(); $headers->removeHeader('Content-Type'); $headers->removeHeader('Content-Transfer-Encoding'); $airsync_body->data = $base->toString(array('headers' => $headers, 'stream' => true)); $airsync_body->estimateddatasize = $base->getBytes(); } else { // Signed/Encrypted message - can't mess with it at all. $raw = new Horde_ActiveSync_Rfc822($imap_message->getFullMsg(true), false); $airsync_body->estimateddatasize = $raw->getBytes(); $airsync_body->data = $raw->getString(); $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } $airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_MIME; // MIME Truncation // @todo Remove this sanity-check hack in 3.0. This is needed // since truncationsize incorrectly defaulted to a // MIME_TRUNCATION constant and could be cached in the sync-cache. $ts = !empty($options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]['truncationsize']) ? $options['bodyprefs'][Horde_ActiveSync::BODYPREF_TYPE_MIME]['truncationsize'] : false; $mime_truncation = !empty($ts) && $ts > 9 ? $ts : (!empty($options['truncation']) && $options['truncation'] > 9 ? $options['truncation'] : false); $this->_logger->info(sprintf('[%s] Checking MIMETRUNCATION: %s, ServerData: %s', $this->_procid, $mime_truncation, $airsync_body->estimateddatasize)); if (!empty($mime_truncation) && $airsync_body->estimateddatasize > $mime_truncation) { ftruncate($airsync_body->data, $mime_truncation); $airsync_body->truncated = '1'; } else { $airsync_body->truncated = '0'; } $eas_message->airsyncbasebody = $airsync_body; } elseif ($body_type_pref == Horde_ActiveSync::BODYPREF_TYPE_HTML) { // Sending non MIME encoded HTML message text. $eas_message->airsyncbasebody = $this->_buildHtmlPart($mbd, $airsync_body); $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } elseif ($body_type_pref == Horde_ActiveSync::BODYPREF_TYPE_PLAIN) { // Non MIME encoded plaintext $this->_logger->info(sprintf('[%s] Sending PLAINTEXT Message.', $this->_procid)); if (!empty($mbd->plain['size'])) { $airsync_body->estimateddatasize = $mbd->plain['size']; $airsync_body->truncated = $mbd->plain['truncated']; $airsync_body->data = $mbd->plain['body']->stream; $airsync_body->type = Horde_ActiveSync::BODYPREF_TYPE_PLAIN; $eas_message->airsyncbasebody = $airsync_body; } $eas_message->airsyncbaseattachments = $imap_message->getAttachments($version); } // It's legal to have both a BODY and a BODYPART, so we must also // check for that. if ($version > Horde_ActiveSync::VERSION_FOURTEEN && !empty($options['bodypartprefs'])) { $body_part = Horde_ActiveSync::messageFactory('AirSyncBaseBodypart'); $eas_message->airsyncbasebodypart = $this->_buildBodyPart($mbd, $options, $body_part); } if ($version > Horde_ActiveSync::VERSION_TWELVEONE) { $flags = array(); $msgFlags = $this->_getMsgFlags(); foreach ($imap_message->getFlags() as $flag) { if (!empty($msgFlags[Horde_String::lower($flag)])) { $flags[] = $msgFlags[Horde_String::lower($flag)]; } } $eas_message->categories = $flags; } } // Body Preview? Note that this is different from BodyPart's preview if ($version >= Horde_ActiveSync::VERSION_FOURTEEN && !empty($options['bodyprefs']['preview'])) { $mbd->plain['body']->rewind(); $eas_message->airsyncbasebody->preview = $mbd->plain['body']->substring(0, $options['bodyprefs']['preview']); } $mbd = null; // Check for special message types. if ($imap_message->isEncrypted()) { $eas_message->messageclass = 'IPM.Note.SMIME'; } elseif ($imap_message->isSigned()) { $eas_message->messageclass = 'IPM.Note.SMIME.MultipartSigned'; } $part = $imap_message->getStructure(); if ($part->getType() == 'multipart/report') { $ids = array_keys($imap_message->contentTypeMap()); reset($ids); $part1_id = next($ids); $part2_id = Horde_Mime::mimeIdArithmetic($part1_id, 'next'); $lines = explode(chr(13), $imap_message->getBodyPart($part2_id, array('decode' => true))); switch ($part->getContentTypeParameter('report-type')) { case 'delivery-status': foreach ($lines as $line) { if (strpos(trim($line), 'Action:') === 0) { switch (trim(substr(trim($line), 7))) { case 'failed': $eas_message->messageclass = 'REPORT.IPM.NOTE.NDR'; break 2; case 'delayed': $eas_message->messageclass = 'REPORT.IPM.NOTE.DELAYED'; break 2; case 'delivered': $eas_message->messageclass = 'REPORT.IPM.NOTE.DR'; break 2; } } } break; case 'disposition-notification': foreach ($lines as $line) { if (strpos(trim($line), 'Disposition:') === 0) { if (strpos($line, 'displayed') !== false) { $eas_message->messageclass = 'REPORT.IPM.NOTE.IPNRN'; } elseif (strpos($line, 'deleted') !== false) { $eas_message->messageclass = 'REPORT.IPM.NOTE.IPNNRN'; } break; } } } } $part = null; // Check for meeting requests and POOMMAIL_FLAG data if ($version >= Horde_ActiveSync::VERSION_TWELVE) { $eas_message->contentclass = 'urn:content-classes:message'; if ($mime_part = $imap_message->hasiCalendar()) { $data = Horde_ActiveSync_Utils::ensureUtf8($mime_part->getContents(), $mime_part->getCharset()); $vCal = new Horde_Icalendar(); if ($vCal->parsevCalendar($data, 'VCALENDAR', $mime_part->getCharset())) { $classes = $vCal->getComponentClasses(); } else { $classes = array(); } if (!empty($classes['horde_icalendar_vevent'])) { try { $method = $vCal->getAttribute('METHOD'); $eas_message->contentclass = 'urn:content-classes:calendarmessage'; } catch (Horde_Icalendar_Exception $e) { } switch ($method) { case 'REQUEST': case 'PUBLISH': $eas_message->messageclass = 'IPM.Schedule.Meeting.Request'; $mtg = Horde_ActiveSync::messageFactory('MeetingRequest'); $mtg->fromvEvent($vCal); $eas_message->meetingrequest = $mtg; break; case 'REPLY': try { $reply_status = $this->_getiTipStatus($vCal); switch ($reply_status) { case 'ACCEPTED': $eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Pos'; break; case 'DECLINED': $eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Neg'; break; case 'TENTATIVE': $eas_message->messageclass = 'IPM.Schedule.Meeting.Resp.Tent'; } $mtg = Horde_ActiveSync::messageFactory('MeetingRequest'); $mtg->fromvEvent($vCal); $eas_message->meetingrequest = $mtg; } catch (Horde_ActiveSync_Exception $e) { $this->_logger->err($e->getMessage()); } } } } if ($imap_message->getFlag(Horde_Imap_Client::FLAG_FLAGGED)) { $poommail_flag = Horde_ActiveSync::messageFactory('Flag'); $poommail_flag->subject = $imap_message->getSubject(); $poommail_flag->flagstatus = Horde_ActiveSync_Message_Flag::FLAG_STATUS_ACTIVE; $poommail_flag->flagtype = Horde_Imap_Client::FLAG_FLAGGED; $eas_message->flag = $poommail_flag; } } if ($version >= Horde_ActiveSync::VERSION_FOURTEEN) { $eas_message->messageid = $imap_message->getHeaders()->getValue('Message-ID'); $eas_message->forwarded = $imap_message->getFlag(Horde_Imap_Client::FLAG_FORWARDED); $eas_message->answered = $imap_message->getFlag(Horde_Imap_Client::FLAG_ANSWERED); } $imap_message = null; return $eas_message; }
/** * Generates a new MIME messages that will wrap a Kolab groupware object. * * @return Horde_Mime_Part The new MIME message. */ protected function createEnvelope() { $envelope = new Horde_Mime_Part(); $envelope->setName('Kolab Groupware Data'); $envelope->setType('multipart/mixed'); $description = new Horde_Mime_Part(); $description->setName('Kolab Groupware Information'); $description->setType('text/plain'); $description->setDisposition('inline'); $description->setCharset('utf-8'); $description->setContents(sprintf(Horde_Kolab_Storage_Translation::t("This is a Kolab Groupware object. To view this object you will need an email client that understands the Kolab Groupware format. For a list of such email clients please visit %s"), 'http://www.kolab.org/content/kolab-clients'), array('encoding' => 'quoted-printable')); $envelope->addPart($description); return $envelope; }
/** * Recursively parse BODYSTRUCTURE data from a FETCH return (see * RFC 3501 [7.4.2]). * * @param Horde_Imap_Client_Tokenize $data Data returned from the server. * * @return array The array of bodystructure information. */ protected function _parseBodystructure(Horde_Imap_Client_Tokenize $data) { $ob = new Horde_Mime_Part(); // If index 0 is an array, this is a multipart part. if (is_object($entry = $data->rewind())) { // Keep going through array values until we find a non-array. do { $ob->addPart($this->_parseBodystructure($entry)); } while (is_object($entry = $data->next())); // The first string entry after an array entry gives us the // subpart type. $ob->setType('multipart/' . $entry); // After the subtype is further extension information. This // information MAY not appear for BODYSTRUCTURE requests. // This is parameter information. if (is_object($tmp = $data->next())) { foreach ($this->_parseStructureParams($tmp, 'content-type') as $key => $val) { $ob->setContentTypeParameter($key, $val); } } } else { $ob->setType($entry . '/' . $data->next()); if (is_object($tmp = $data->next())) { foreach ($this->_parseStructureParams($tmp, 'content-type') as $key => $val) { $ob->setContentTypeParameter($key, $val); } } if (!is_null($tmp = $data->next())) { $ob->setContentId($tmp); } if (!is_null($tmp = $data->next())) { $ob->setDescription(Horde_Mime::decode($tmp)); } if (!is_null($tmp = $data->next())) { $ob->setTransferEncoding($tmp); } $ob->setBytes($data->next()); // If the type is 'message/rfc822' or 'text/*', several extra // fields are included switch ($ob->getPrimaryType()) { case 'message': if ($ob->getSubType() == 'rfc822') { $data->next(); // Ignore: envelope $ob->addPart($this->_parseBodystructure($data->next())); $data->next(); // Ignore: lines } break; case 'text': $data->next(); // Ignore: lines break; } // After the subtype is further extension information. This // information MAY appear for BODYSTRUCTURE requests. $data->next(); // Ignore: MD5 } // This is disposition information if (is_object($tmp = $data->next())) { $ob->setDisposition($tmp->rewind()); foreach ($this->_parseStructureParams($tmp->next(), 'content-disposition') as $key => $val) { $ob->setDispositionParameter($key, $val); } } // This is language information. It is either a single value or a list // of values. if (($tmp = $data->next()) !== false) { $ob->setLanguage($tmp); } $data->next(); // Ignore: location (RFC 2557) return $ob; }
/** * Creates a MIME object from the text of one part of a MIME message. * * @param string $header The header text. * @param string $body The body text. * @param array $opts Additional options: * <pre> * - ctype: (string) The default content-type. * - forcemime: (boolean) If true, the message data is assumed to be * MIME data. If not, a MIME-Version header must exist to * be parsed as a MIME message. * - level: (integer) Current nesting level. * - no_body: (boolean) If true, don't set body contents of parts. * </pre> * * @return Horde_Mime_Part The MIME part object. */ protected static function _getStructure($header, $body, array $opts = array()) { $opts = array_merge(array('ctype' => 'application/octet-stream', 'forcemime' => false, 'level' => 0, 'no_body' => false), $opts); /* Parse headers text into a Horde_Mime_Headers object. */ $hdrs = Horde_Mime_Headers::parseHeaders($header); $ob = new Horde_Mime_Part(); /* This is not a MIME message. */ if (!$opts['forcemime'] && !$hdrs->getValue('mime-version')) { $ob->setType('text/plain'); if ($len = strlen($body)) { if ($opts['no_body']) { $ob->setBytes($len); } else { $ob->setContents($body); } } return $ob; } /* Content type. */ if ($tmp = $hdrs->getValue('content-type', Horde_Mime_Headers::VALUE_BASE)) { $ob->setType($tmp); $ctype_params = $hdrs->getValue('content-type', Horde_Mime_Headers::VALUE_PARAMS); foreach ($ctype_params as $key => $val) { $ob->setContentTypeParameter($key, $val); } } else { $ob->setType($opts['ctype']); } /* Content transfer encoding. */ if ($tmp = $hdrs->getValue('content-transfer-encoding')) { $ob->setTransferEncoding($tmp); } /* Content-Description. */ if ($tmp = $hdrs->getValue('content-description')) { $ob->setDescription($tmp); } /* Content-Disposition. */ if ($tmp = $hdrs->getValue('content-disposition', Horde_Mime_Headers::VALUE_BASE)) { $ob->setDisposition($tmp); foreach ($hdrs->getValue('content-disposition', Horde_Mime_Headers::VALUE_PARAMS) as $key => $val) { $ob->setDispositionParameter($key, $val); } } /* Content-Duration */ if ($tmp = $hdrs->getValue('content-duration')) { $ob->setDuration($tmp); } /* Content-ID. */ if ($tmp = $hdrs->getValue('content-id')) { $ob->setContentId($tmp); } if (($len = strlen($body)) && $ob->getPrimaryType() != 'multipart') { if ($opts['no_body']) { $ob->setBytes($len); } else { $ob->setContents($body); } } if (++$opts['level'] >= self::NESTING_LIMIT) { return $ob; } /* Process subparts. */ switch ($ob->getPrimaryType()) { case 'message': if ($ob->getSubType() == 'rfc822') { $ob->addPart(self::parseMessage($body, array('forcemime' => true))); } break; case 'multipart': $boundary = $ob->getContentTypeParameter('boundary'); if (!is_null($boundary)) { foreach (self::_findBoundary($body, 0, $boundary) as $val) { if (!isset($val['length'])) { break; } $subpart = substr($body, $val['start'], $val['length']); $hdr_pos = self::_findHeader($subpart, self::EOL); $ob->addPart(self::_getStructure(substr($subpart, 0, $hdr_pos), substr($subpart, $hdr_pos + 2), array('ctype' => $ob->getSubType() == 'digest' ? 'message/rfc822' : 'text/plain', 'forcemime' => true, 'level' => $opts['level'], 'no_body' => $opts['no_body']))); } } break; } return $ob; }
/** * Sign a MIME part using S/MIME. This produces S/MIME Version 3.2 * compatible data (see RFC 5751 [3.4]). * * @param Horde_Mime_Part $mime_part The object to sign. * @param array $params The parameters required for signing. * * @return Horde_Mime_Part A signed MIME part object. * @throws Horde_Crypt_Exception */ public function signMIMEPart($mime_part, $params) { /* Sign the part as a message */ $message = $this->encrypt($mime_part->toString(array('headers' => true, 'canonical' => true)), $params); /* Break the result into its components */ $mime_message = Horde_Mime_Part::parseMessage($message, array('forcemime' => true)); $smime_sign = $mime_message->getPart('2'); $smime_sign->setDescription(Horde_Crypt_Translation::t("S/MIME Signature")); $smime_sign->setTransferEncoding('base64', array('send' => true)); $smime_part = new Horde_Mime_Part(); $smime_part->setType('multipart/signed'); $smime_part->setContents("This is a cryptographically signed message in MIME format.\n"); $smime_part->setContentTypeParameter('protocol', 'application/pkcs7-signature'); // Per RFC 5751 [3.4.3.2], 'sha1' has been deprecated for 'sha-1'. $smime_part->setContentTypeParameter('micalg', 'sha-1'); $smime_part->addPart($mime_part); $smime_part->addPart($smime_sign); return $smime_part; }
/** * Generate the MDN according to the specifications listed in RFC * 3798 [3]. * * @param boolean $action Was this MDN type a result of a manual * action on part of the user? * @param boolean $sending Was this MDN sent as a result of a manual * action on part of the user? * @param string $type The type of action performed by the user. * Per RFC 3798 [3.2.6.2] the following types are * valid: * - deleted * - displayed * @param string $name The name of the local server. * @param Mail $mailer A Mail driver. * @param array $opts Additional options: * - charset: (string) Default charset. * DEFAULT: NONE * - from_addr: (string) From address. * DEFAULT: NONE * @param array $mod The list of modifications. Per RFC 3798 * [3.2.6.3] the following modifications are * valid: * - error * @param array $err If $mod is 'error', the additional * information to provide. Key is the type of * modification, value is the text. * * @throws Horde_Mime_Exception */ public function generate($action, $sending, $type, $name, $mailer, array $opts = array(), array $mod = array(), array $err = array()) { $opts = array_merge(array('charset' => null, 'from_addr' => null), $opts); $to = $this->getMdnReturnAddr(); $ua = $this->_headers->getUserAgent(); $orig_recip = $this->_headers->getValue('Original-Recipient'); if (!empty($orig_recip) && is_array($orig_recip)) { $orig_recip = $orig_recip[0]; } $msg_id = $this->_headers->getValue('Message-ID'); /* Create the Disposition field now (RFC 3798 [3.2.6]). */ $dispo = 'Disposition: ' . ($action ? 'manual-action' : 'automatic-action') . '/' . ($sending ? 'MDN-sent-manually' : 'MDN-sent-automatically') . '; ' . $type; if (!empty($mod)) { $dispo .= '/' . implode(', ', $mod); } /* Set up the mail headers. */ $msg_headers = new Horde_Mime_Headers(); $msg_headers->addMessageIdHeader(); $msg_headers->addUserAgentHeader($ua); $msg_headers->addHeader('Date', date('r')); if ($opts['from_addr']) { $msg_headers->addHeader('From', $opts['from_addr']); } $msg_headers->addHeader('To', $this->getMdnReturnAddr()); $msg_headers->addHeader('Subject', Horde_Mime_Translation::t("Disposition Notification")); /* MDNs are a subtype of 'multipart/report'. */ $msg = new Horde_Mime_Part(); $msg->setType('multipart/report'); $msg->setContentTypeParameter('report-type', 'disposition-notification'); /* The first part is a human readable message. */ $part_one = new Horde_Mime_Part(); $part_one->setType('text/plain'); $part_one->setCharset($opts['charset']); if ($type == 'displayed') { $contents = sprintf(Horde_Mime_Translation::t("The message sent on %s to %s with subject \"%s\" has been displayed.\n\nThis is no guarantee that the message has been read or understood."), $this->_headers->getValue('Date'), $this->_headers->getValue('To'), $this->_headers->getValue('Subject')); $flowed = new Horde_Text_Flowed($contents, $opts['charset']); $flowed->setDelSp(true); $part_one->setContentTypeParameter('format', 'flowed'); $part_one->setContentTypeParameter('DelSp', 'Yes'); $part_one->setContents($flowed->toFlowed()); } // TODO: Messages for other notification types. $msg->addPart($part_one); /* The second part is a machine-parseable description. */ $part_two = new Horde_Mime_Part(); $part_two->setType('message/disposition-notification'); $part_two_text = array('Reporting-UA: ' . $name . '; ' . $ua . "\n"); if (!empty($orig_recip)) { $part_two_text[] = 'Original-Recipient: rfc822;' . $orig_recip . "\n"; } if ($opts['from_addr']) { $part_two_text[] = 'Final-Recipient: rfc822;' . $opts['from_addr'] . "\n"; } if (!empty($msg_id)) { $part_two_text[] = 'Original-Message-ID: rfc822;' . $msg_id . "\n"; } $part_two_text[] = $dispo . "\n"; if (in_array('error', $mod) && isset($err['error'])) { $part_two_text[] = 'Error: ' . $err['error'] . "\n"; } $part_two->setContents($part_two_text); $msg->addPart($part_two); /* The third part is the text of the original message. RFC 3798 [3] * allows us to return only a portion of the entire message - this * is left up to the user. */ $part_three = new Horde_Mime_Part(); $part_three->setType('message/rfc822'); $part_three_text = array($this->_headers->toString()); if (!empty($this->_msgtext)) { $part_three_text[] = $part_three->getEOL() . $this->_msgtext; } $part_three->setContents($part_three_text); $msg->addPart($part_three); return $msg->send($to, $msg_headers, $mailer); }
/** * Creates a structure object from the text of one part of a MIME message. * * @param string $header The header text. * @param string $body The body text. * @param string $ctype The default content-type. * @param boolean $forcemime If true, the message data is assumed to be * MIME data. If not, a MIME-Version header * must exist to be parsed as a MIME message. * * @return Horde_Mime_Part TODO */ protected static function _getStructure($header, $body, $ctype = 'application/octet-stream', $forcemime = false) { /* Parse headers text into a Horde_Mime_Headers object. */ $hdrs = Horde_Mime_Headers::parseHeaders($header); $ob = new Horde_Mime_Part(); /* This is not a MIME message. */ if (!$forcemime && !$hdrs->getValue('mime-version')) { $ob->setType('text/plain'); if (!empty($body)) { $ob->setContents($body); $ob->setBytes(strlen(str_replace(array("\r\n", "\n"), array("\n", "\r\n"), $body))); } return $ob; } /* Content type. */ if ($tmp = $hdrs->getValue('content-type', Horde_Mime_Headers::VALUE_BASE)) { $ob->setType($tmp); $ctype_params = $hdrs->getValue('content-type', Horde_Mime_Headers::VALUE_PARAMS); foreach ($ctype_params as $key => $val) { $ob->setContentTypeParameter($key, $val); } } else { $ob->setType($ctype); $ctype_params = array(); } /* Content transfer encoding. */ if ($tmp = $hdrs->getValue('content-transfer-encoding')) { $ob->setTransferEncoding($tmp); } /* Content-Description. */ if ($tmp = $hdrs->getValue('content-description')) { $ob->setDescription($tmp); } /* Content-Disposition. */ if ($tmp = $hdrs->getValue('content-disposition', Horde_Mime_Headers::VALUE_BASE)) { $ob->setDisposition($tmp); foreach ($hdrs->getValue('content-disposition', Horde_Mime_Headers::VALUE_PARAMS) as $key => $val) { $ob->setDispositionParameter($key, $val); } } /* Content-Duration */ if ($tmp = $hdrs->getValue('content-duration')) { $ob->setDuration($tmp); } /* Content-ID. */ if ($tmp = $hdrs->getValue('content-id')) { $ob->setContentId($tmp); } /* Get file size (if 'body' text is set). */ if (!empty($body) && $ob->getPrimaryType() != 'multipart') { $ob->setContents($body); if ($ob->getType() != '/message/rfc822') { $ob->setBytes(strlen(str_replace(array("\r\n", "\n"), array("\n", "\r\n"), $body))); } } /* Process subparts. */ switch ($ob->getPrimaryType()) { case 'message': if ($ob->getSubType() == 'rfc822') { $ob->addPart(self::parseMessage($body, array('forcemime' => true))); } break; case 'multipart': if (isset($ctype_params['boundary'])) { $b_find = self::_findBoundary($body, 0, $ctype_params['boundary']); foreach ($b_find as $val) { $subpart = substr($body, $val['start'], $val['length']); list($hdr_pos, $eol) = self::_findHeader($subpart); $ob->addPart(self::_getStructure(substr($subpart, 0, $hdr_pos), substr($subpart, $hdr_pos + $eol), $ob->getSubType() == 'digest' ? 'message/rfc822' : 'text/plain', true)); } } break; } return $ob; }
/** * Converts an HTML part to a multipart/related part, if necessary. * * @param Horde_Domhtml $html HTML data. * @param Horde_Mime_Part $part The HTML part. * * @return Horde_Mime_Part The part to add to the compose output. */ protected function _convertToRelated(Horde_Domhtml $html, Horde_Mime_Part $part) { $r_part = false; foreach ($this as $atc) { if ($atc->related) { $r_part = true; break; } } if (!$r_part) { return $part; } /* Create new multipart/related part. */ $related = new Horde_Mime_Part(); $related->setType('multipart/related'); /* Get the CID for the 'root' part. Although by default the first part * is the root part (RFC 2387 [3.2]), we may as well be explicit and * put the CID in the 'start' parameter. */ $related->setContentTypeParameter('start', $part->setContentId()); $related->addPart($part); /* HTML iteration is from child->parent, so need to gather related * parts and add at end after sorting to generate a more sensible * attachment list. */ $add = array(); foreach ($html as $node) { if ($node instanceof DOMElement && $node->hasAttribute(self::RELATED_ATTR)) { list($attr_name, $atc_id) = explode(';', $node->getAttribute(self::RELATED_ATTR)); /* If attachment can't be found, ignore. */ if ($r_atc = $this[$atc_id]) { if ($r_atc->linked) { $attr = strval($r_atc->link_url); } else { $related_part = $r_atc->getPart(true); $attr = 'cid:' . $related_part->setContentId(); $add[] = $related_part; } $node->setAttribute($attr_name, $attr); } $node->removeAttribute(self::RELATED_ATTR); } } array_map(array($related, 'addPart'), array_reverse($add)); return $related; }
/** * Builds the body MIME part of a multipart message. * * @param Horde_View $view A view to render the HTML and plain text * templates for the messate. * @param string $template The template base name for the view. * @param Horde_Mime_Part $image The MIME part of a related image. * * @return Horde_Mime_Part A multipart/alternative MIME part. */ public static function buildMimeMessage(Horde_View $view, $template, Horde_Mime_Part $image) { $multipart = new Horde_Mime_Part(); $multipart->setType('multipart/alternative'); $bodyText = new Horde_Mime_Part(); $bodyText->setType('text/plain'); $bodyText->setCharset('UTF-8'); $bodyText->setContents($view->render($template . '.plain.php')); $bodyText->setDisposition('inline'); $multipart->addPart($bodyText); $bodyHtml = new Horde_Mime_Part(); $bodyHtml->setType('text/html'); $bodyHtml->setCharset('UTF-8'); $bodyHtml->setContents($view->render($template . '.html.php')); $bodyHtml->setDisposition('inline'); $related = new Horde_Mime_Part(); $related->setType('multipart/related'); $related->setContentTypeParameter('start', $bodyHtml->setContentId()); $related->addPart($bodyHtml); $related->addPart($image); $multipart->addPart($related); return $multipart; }
/** * Parses an armored message into a Horde_Mime_Part object. * * @param mixed $text Either the text to parse or a Horde_Stream object. * * @return mixed Either null if no PGP data was found, or a * Horde_Mime_Part object. For detached signature data: * the full contents of the armored text (data + sig) is * contained in the SIG_RAW metadata, and the charset is * contained in the SIG_CHARSET metadata, within the * application/pgp-signature part. */ public function parseToPart($text, $charset = 'UTF-8') { $parts = $this->parse($text); if (empty($parts) || count($parts) == 1 && $parts[0]['type'] == self::ARMOR_TEXT) { return null; } $new_part = new Horde_Mime_Part(); $new_part->setType('multipart/mixed'); foreach ($parts as $val) { switch ($val['type']) { case self::ARMOR_TEXT: $part = new Horde_Mime_Part(); $part->setType('text/plain'); $part->setCharset($charset); $part->setContents(implode("\n", $val['data'])); $new_part->addPart($part); break; case self::ARMOR_PUBLIC_KEY: $part = new Horde_Mime_Part(); $part->setType('application/pgp-keys'); $part->setContents(implode("\n", $val['data'])); $new_part->addPart($part); break; case self::ARMOR_MESSAGE: $part = new Horde_Mime_Part(); $part->setType('multipart/encrypted'); $part->setMetadata(self::PGP_ARMOR, true); $part->setContentTypeParameter('protocol', 'application/pgp-encrypted'); $part1 = new Horde_Mime_Part(); $part1->setType('application/pgp-encrypted'); $part1->setContents("Version: 1\n"); $part2 = new Horde_Mime_Part(); $part2->setType('application/octet-stream'); $part2->setContents(implode("\n", $val['data'])); $part2->setDisposition('inline'); $part->addPart($part1); $part->addPart($part2); $new_part->addPart($part); break; case self::ARMOR_SIGNED_MESSAGE: if (($sig = current($parts)) && $sig['type'] == self::ARMOR_SIGNATURE) { $part = new Horde_Mime_Part(); $part->setType('multipart/signed'); // TODO: add micalg parameter $part->setContentTypeParameter('protocol', 'application/pgp-signature'); $part1 = new Horde_Mime_Part(); $part1->setType('text/plain'); $part1->setCharset($charset); $part1_data = implode("\n", $val['data']); $part1->setContents(substr($part1_data, strpos($part1_data, "\n\n") + 2)); $part2 = new Horde_Mime_Part(); $part2->setType('application/pgp-signature'); $part2->setContents(implode("\n", $sig['data'])); $part2->setMetadata(self::SIG_CHARSET, $charset); $part2->setMetadata(self::SIG_RAW, implode("\n", $val['data']) . "\n" . implode("\n", $sig['data'])); $part->addPart($part1); $part->addPart($part2); $new_part->addPart($part); next($parts); } } } return $new_part; }
/** * Scan text for UUencode data an, if it exists, convert the part to the * embedded MIME representation. * * @return mixed See self::_getEmbeddedMimeParts(). */ protected function _parseUUencode() { $text = Horde_String::convertCharset($this->_mimepart->getContents(), $this->_mimepart->getCharset(), 'UTF-8'); $files = Horde_Mime::uudecode($text); if (empty($files)) { return null; } $new_part = new Horde_Mime_Part(); $new_part->setType('multipart/mixed'); $text_part = new Horde_Mime_Part(); $text_part->setType('text/plain'); $text_part->setCharset($this->getConfigParam('charset')); $text_part->setContents(preg_replace("/begin [0-7]{3} .+\r?\n.+\r?\nend/Us", "\n", $text)); $new_part->addPart($text_part); reset($files); while (list(, $file) = each($files)) { $uupart = new Horde_Mime_Part(); $uupart->setType('application/octet-stream'); $uupart->setContents($file['data']); $uupart->setName(strip_tags($file['name'])); $new_part->addPart($uupart); } return $new_part; }
$imgpart = new Horde_Mime_Part(); $imgpart->setType($image->getType('screen')); $imgpart->setContents($image->raw('screen')); $img_tag = '<img src="cid:' . $imgpart->setContentID() . '" /><p />'; $comments = $htmlpart->replaceEOL(Horde_Util::getFormData('ecard_comments')); if (!Horde_Util::getFormData('rtemode')) { $comments = '<pre>' . htmlspecialchars($comments, ENT_COMPAT, 'UTF-8') . '</pre>'; } $htmlpart->setContents('<html>' . $img_tag . $comments . '</html>'); $related->setContentTypeParameter('start', $htmlpart->setContentID()); $related->addPart($htmlpart); $related->addPart($imgpart); /* Create the multipart/alternative part. */ $alternative = new Horde_Mime_Part(); $alternative->setType('multipart/alternative'); $alternative->addPart($textpart); $alternative->addPart($related); /* Add them to the mail message */ $alt = new Horde_Mime_Mail(array('Subject' => _("Ecard - ") . Horde_Util::getFormData('image_desc'), 'To' => $to, 'From' => $from)); $alt->setBasePart($alternative); /* Send. */ try { $alt->send($injector->getInstance('Horde_Mail')); } catch (Horde_Mime_Exception $e) { $notification->push(sprintf(_("There was an error sending your message: %s"), $e->getMessage()), 'horde.error'); } echo Horde::wrapInlineScript(array('window.close();')); exit; } $title = sprintf(_("Send Ecard :: %s"), $image->filename); /* Set up the form object. */
/** * Parse the output from imap_fetchstructure() into a MIME Part object. * * @param object $data Data from imap_fetchstructure(). * * @return Horde_Mime_Part A MIME Part object. */ protected function _parseStructure($data) { $ob = new Horde_Mime_Part(); $ob->setType(Horde_String::lower($data->type) . '/' . Horde_String::lower($data->subType)); // Optional for multipart-parts, required for all others if (isset($data->parameters)) { $params = array(); foreach ($data->parameters as $key => $value) { $params[Horde_String::lower($key)] = $value; } $params = Horde_Mime::decodeParam('content-type', $params); foreach ($params['params'] as $key => $value) { $ob->setContentTypeParameter($key, $value); } } // Optional entries. 'location' and 'language' not supported if (isset($data->disposition)) { $ob->setDisposition($data->disposition); if (isset($data->dparameters)) { $dparams = array(); foreach ($data->dparameters as $key => $value) { $dparams[Horde_String::lower($key)] = $value; } $dparams = Horde_Mime::decodeParam('content-disposition', $dparams); foreach ($dparams['params'] as $key => $value) { $ob->setDispositionParameter($key, $value); } } } if ($ob->getPrimaryType() == 'multipart') { // multipart/* specific entries foreach ($data->subParts as $val) { $ob->addPart($this->_parseStructure($val)); } } else { // Required options if (isset($data->partID)) { $ob->setContentId($data->partID); } $ob->setTransferEncoding(Horde_String::lower($data->encoding)); $ob->setBytes($data->bytes); if ($ob->getType() == 'message/rfc822') { $ob->addPart($this->_parseStructure(reset($data->subParts))); } } return $ob; }
/** * Convert a TNEF attachment into a multipart/mixed part. * * @param integer|Horde_Mime_part $data Either a mime part id or a * Horde_Mime_Part object containing * the TNEF attachment. * * @return Horde_Mime_Part The multipart/mixed MIME part containing any * attachment data we can decode. */ protected function _decodeTnefData($data) { $wrapper = new Horde_Mime_Part(); $wrapper->setType('multipart/mixed'); if (!$data instanceof Horde_Mime_Part) { $mime_part = $this->getMimePart($data); } else { $mime_part = $data; } $tnef_parser = Horde_Compress::factory('Tnef'); try { $tnef_data = $tnef_parser->decompress($mime_part->getContents()); } catch (Horde_Compress_Exception $e) { return false; } if (!count($tnef_data)) { return false; } reset($tnef_data); while (list(, $data) = each($tnef_data)) { $tmp_part = new Horde_Mime_Part(); $tmp_part->setName($data['name']); $tmp_part->setDescription($data['name']); $tmp_part->setContents($data['stream']); $type = $data['type'] . '/' . $data['subtype']; if (in_array($type, array('application/octet-stream', 'application/base64'))) { $type = Horde_Mime_Magic::filenameToMIME($data['name']); } $tmp_part->setType($type); $wrapper->addPart($tmp_part); } return $wrapper; }
private function getMultipartMimeMessage($mime_type) { $envelope = new Horde_Mime_Part(); $envelope->setType('multipart/mixed'); $foo = new Horde_Mime_Part(); $foo->setType('foo/bar'); $envelope->addPart($foo); $kolab = new Horde_Mime_Part(); $kolab->setType($mime_type); $envelope->addPart($kolab); $envelope->buildMimeIds(); return $envelope; }
/** * Parse the output from imap_fetchstructure() into a MIME Part object. * * @param object $data Data from imap_fetchstructure(). * * @return Horde_Mime_Part A MIME Part object. */ protected function _parseStructure($data) { $ob = new Horde_Mime_Part(); $ob->setType($this->_mimeTypes[$data->type] . '/' . ($data->ifsubtype ? strtolower($data->subtype) : Horde_Mime_Part::UNKNOWN)); // Optional for multipart-parts, required for all others if ($data->ifparameters) { $params = array(); foreach ($data->parameters as $val) { $params[$val->attribute] = $val->value; } $params = Horde_Mime::decodeParam('content-type', $params); foreach ($params['params'] as $key => $val) { $ob->setContentTypeParameter($key, $val); } } // Optional entries. 'location' and 'language' not supported if ($data->ifdisposition) { $ob->setDisposition($data->disposition); if ($data->ifdparameters) { $dparams = array(); foreach ($data->dparameters as $val) { $dparams[$val->attribute] = $val->value; } $dparams = Horde_Mime::decodeParam('content-disposition', $dparams); foreach ($dparams['params'] as $key => $val) { $ob->setDispositionParameter($key, $val); } } } if ($ob->getPrimaryType() == 'multipart') { // multipart/* specific entries foreach ($data->parts as $val) { $ob->addPart($this->_parseStructure($val)); } } else { // Required options if ($data->ifid) { $ob->setContentId($data->id); } if ($data->ifdescription) { $ob->setDescription(Horde_Mime::decode($data->description)); } $ob->setTransferEncoding($this->_mimeEncodings[$data->encoding]); $ob->setBytes($data->bytes); if ($ob->getType() == 'message/rfc822') { $ob->addPart($this->_parseStructure(reset($data->parts))); } } return $ob; }
/** * Recursively parse BODYSTRUCTURE data from a FETCH return (see * RFC 3501 [7.4.2]). * * @param Horde_Imap_Client_Tokenize $data Data returned from the server. * * @return Horde_Mime_Part Mime part object. */ protected function _parseBodystructure(Horde_Imap_Client_Tokenize $data) { $ob = new Horde_Mime_Part(); // If index 0 is an array, this is a multipart part. if (($entry = $data->next()) === true) { do { $ob->addPart($this->_parseBodystructure($data)); } while (($entry = $data->next()) === true); // The subpart type. $ob->setType('multipart/' . $entry); // After the subtype is further extension information. This // information MAY appear for BODYSTRUCTURE requests. // This is parameter information. if (($tmp = $data->next()) === false) { return $ob; } elseif ($tmp === true) { foreach ($this->_parseStructureParams($data, 'content-type') as $key => $val) { $ob->setContentTypeParameter($key, $val); } } } else { $ob->setType($entry . '/' . $data->next()); if ($data->next() === true) { foreach ($this->_parseStructureParams($data, 'content-type') as $key => $val) { $ob->setContentTypeParameter($key, $val); } } if (!is_null($tmp = $data->next())) { $ob->setContentId($tmp); } if (!is_null($tmp = $data->next())) { $ob->setDescription(Horde_Mime::decode($tmp)); } if (!is_null($tmp = $data->next())) { $ob->setTransferEncoding($tmp); } $ob->setBytes($data->next()); // If the type is 'message/rfc822' or 'text/*', several extra // fields are included switch ($ob->getPrimaryType()) { case 'message': if ($ob->getSubType() == 'rfc822') { if ($data->next() === true) { // Ignore: envelope $data->flushIterator(false); } if ($data->next() === true) { $ob->addPart($this->_parseBodystructure($data)); } $data->next(); // Ignore: lines } break; case 'text': $data->next(); // Ignore: lines break; } // After the subtype is further extension information. This // information MAY appear for BODYSTRUCTURE requests. // Ignore: MD5 if ($data->next() === false) { return $ob; } } // This is disposition information if (($tmp = $data->next()) === false) { return $ob; } elseif ($tmp === true) { $ob->setDisposition($data->next()); if ($data->next() === true) { foreach ($this->_parseStructureParams($data, 'content-disposition') as $key => $val) { $ob->setDispositionParameter($key, $val); } } $data->next(); } // This is language information. It is either a single value or a list // of values. if (($tmp = $data->next()) === false) { return $ob; } elseif (!is_null($tmp)) { $ob->setLanguage($tmp === true ? $data->flushIterator() : $tmp); } // Ignore location (RFC 2557) and consume closing paren. $data->flushIterator(false); return $ob; }
/** * Sends this message. * * @param Mail $mailer A Mail object. * @param boolean $resend If true, the message id and date are re-used; * If false, they will be updated. * @param boolean $flowed Send message in flowed text format. * * @throws Horde_Mime_Exception */ public function send($mailer, $resend = false, $flowed = true) { /* Add mandatory headers if missing. */ $has_header = $this->_headers->getValue('Message-ID'); if (!$resend || !$has_header) { if ($has_header) { $this->_headers->removeHeader('Message-ID'); } $this->_headers->addMessageIdHeader(); } if (!$this->_headers->getValue('User-Agent')) { $this->_headers->addUserAgentHeader(); } $has_header = $this->_headers->getValue('Date'); if (!$resend || !$has_header) { if ($has_header) { $this->_headers->removeHeader('Date'); } $this->_headers->addHeader('Date', date('r')); } if (isset($this->_base)) { $basepart = $this->_base; } else { /* Send in flowed format. */ if ($flowed && !empty($this->_body)) { $flowed = new Horde_Text_Flowed($this->_body->getContents(), $this->_body->getCharset()); $flowed->setDelSp(true); $this->_body->setContentTypeParameter('format', 'flowed'); $this->_body->setContentTypeParameter('DelSp', 'Yes'); $this->_body->setContents($flowed->toFlowed()); } /* Build mime message. */ $body = new Horde_Mime_Part(); if (!empty($this->_body) && !empty($this->_htmlBody)) { $body->setType('multipart/alternative'); $this->_body->setDescription(Horde_Mime_Translation::t("Plaintext Version of Message")); $body->addPart($this->_body); $this->_htmlBody->setDescription(Horde_Mime_Translation::t("HTML Version of Message")); $body->addPart($this->_htmlBody); } elseif (!empty($this->_htmlBody)) { $body = $this->_htmlBody; } elseif (!empty($this->_body)) { $body = $this->_body; } if (count($this->_parts)) { $basepart = new Horde_Mime_Part(); $basepart->setType('multipart/mixed'); $basepart->isBasePart(true); if ($body) { $basepart->addPart($body); } foreach ($this->_parts as $mime_part) { $basepart->addPart($mime_part); } } else { $basepart = $body; $basepart->isBasePart(true); } } $basepart->setHeaderCharset($this->_charset); /* Build recipients. */ $recipients = clone $this->_recipients; foreach (array('to', 'cc') as $header) { $recipients->add($this->_headers->getOb($header)); } if ($this->_bcc) { $recipients->add($this->_bcc); } /* Trick Horde_Mime_Part into re-generating the message headers. */ $this->_headers->removeHeader('MIME-Version'); /* Send message. */ $recipients->unique(); $basepart->send($recipients->writeAddress(), $this->_headers, $mailer); /* Remember the basepart */ $this->_base = $basepart; }
/** * Variables required in form input: * - identity (TODO: ? Code uses it, but it is never set anywhere) * - imple_submit: itip_action(s) * - mime_id * - muid * * @return boolean True on success. */ protected function _handle(Horde_Variables $vars) { global $injector, $notification, $registry; $actions = (array) $vars->imple_submit; $result = false; $vCal = new Horde_Icalendar(); /* Retrieve the calendar data from the message. */ try { $contents = $injector->getInstance('IMP_Factory_Contents')->create(new IMP_Indices_Mailbox($vars)); $mime_part = $contents->getMIMEPart($vars->mime_id); if (empty($mime_part)) { throw new IMP_Exception(_("Cannot retrieve calendar data from message.")); } elseif (!$vCal->parsevCalendar($mime_part->getContents(), 'VCALENDAR', $mime_part->getCharset())) { throw new IMP_Exception(_("The calendar data is invalid")); } $components = $vCal->getComponents(); } catch (Exception $e) { $notification->push($e, 'horde.error'); $actions = array(); } foreach ($actions as $key => $action) { $pos = strpos($key, '['); $key = substr($key, $pos + 1, strlen($key) - $pos - 2); switch ($action) { case 'delete': // vEvent cancellation. if ($registry->hasMethod('calendar/delete')) { $guid = $components[$key]->getAttribute('UID'); $recurrenceId = null; try { // This is a cancellation of a recurring event instance. $recurrenceId = $components[$key]->getAttribute('RECURRENCE-ID'); $atts = $components[$key]->getAttribute('RECURRENCE-ID', true); $range = null; foreach ($atts as $att) { if (array_key_exists('RANGE', $att)) { $range = $att['RANGE']; } } } catch (Horde_Icalendar_Exception $e) { } try { $registry->call('calendar/delete', array($guid, $recurrenceId, $range)); $notification->push(_("Event successfully deleted."), 'horde.success'); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error deleting the event: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'update': // vEvent reply. if ($registry->hasMethod('calendar/updateAttendee')) { try { $from = $contents->getHeader()->getOb('from'); $registry->call('calendar/updateAttendee', array($components[$key], $from[0]->bare_address)); $notification->push(_("Respondent Status Updated."), 'horde.success'); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error updating the event: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'import': case 'accept-import': // vFreebusy reply. // vFreebusy publish. // vEvent request. // vEvent publish. // vTodo publish. // vJournal publish. switch ($components[$key]->getType()) { case 'vEvent': $result = $this->_handlevEvent($key, $components, $mime_part); // Must check for exceptions. foreach ($components as $k => $component) { try { if ($component->getType() == 'vEvent' && $component->getAttribute('RECURRENCE-ID')) { $uid = $component->getAttribute('UID'); if ($uid == $components[$key]->getAttribute('UID')) { $this->_handlevEvent($k, $components, $mime_part); } } } catch (Horde_Icalendar_Exception $e) { } } break; case 'vFreebusy': // Import into Kronolith. if ($registry->hasMethod('calendar/import_vfreebusy')) { try { $registry->call('calendar/import_vfreebusy', array($components[$key])); $notification->push(_("The user's free/busy information was sucessfully stored."), 'horde.success'); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error importing user's free/busy information: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'vTodo': // Import into Nag. if ($registry->hasMethod('tasks/import')) { try { $guid = $registry->call('tasks/import', array($components[$key], $mime_part->getType())); $url = Horde::url($registry->link('tasks/show', array('uid' => $guid))); $notification->push(_("The task has been added to your tasklist.") . ' ' . Horde::link($url, _("View task"), null, '_blank') . Horde_Themes_Image::tag('mime/icalendar.png', array('alt' => _("View task"))) . '</a>', 'horde.success', array('content.raw')); $result = true; } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error importing the task: %s"), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'vJournal': default: $notification->push(_("This action is not supported."), 'horde.warning'); } if ($action == 'import') { break; } // Fall-through for 'accept-import' // Fall-through for 'accept-import' case 'accept': case 'deny': case 'tentative': // vEvent request. if (isset($components[$key]) && $components[$key]->getType() == 'vEvent') { $vEvent = $components[$key]; $resource = new Horde_Itip_Resource_Identity($injector->getInstance('IMP_Identity'), $vEvent->getAttribute('ATTENDEE'), $vars->identity); switch ($action) { case 'accept': case 'accept-import': $type = new Horde_Itip_Response_Type_Accept($resource); break; case 'deny': $type = new Horde_Itip_Response_Type_Decline($resource); break; case 'tentative': $type = new Horde_Itip_Response_Type_Tentative($resource); break; } try { // Send the reply. Horde_Itip::factory($vEvent, $resource)->sendMultiPartResponse($type, new Horde_Core_Itip_Response_Options_Horde('UTF-8', array()), $injector->getInstance('IMP_Mail')); $notification->push(_("Reply Sent."), 'horde.success'); $result = true; } catch (Horde_Itip_Exception $e) { $notification->push(sprintf(_("Error sending reply: %s."), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("This action is not supported."), 'horde.warning'); } break; case 'send': case 'reply': case 'reply2m': // vfreebusy request. if (isset($components[$key]) && $components[$key]->getType() == 'vFreebusy') { $vFb = $components[$key]; // Get the organizer details. try { $organizer = parse_url($vFb->getAttribute('ORGANIZER')); } catch (Horde_Icalendar_Exception $e) { break; } $organizerEmail = $organizer['path']; $organizer = $vFb->getAttribute('ORGANIZER', true); $organizerFullEmail = new Horde_Mail_Rfc822_Address($organizerEmail); if (isset($organizer['cn'])) { $organizerFullEmail->personal = $organizer['cn']; } if ($action == 'reply2m') { $startStamp = time(); $endStamp = $startStamp + 60 * 24 * 3600; } else { try { $startStamp = $vFb->getAttribute('DTSTART'); } catch (Horde_Icalendar_Exception $e) { $startStamp = time(); } try { $endStamp = $vFb->getAttribute('DTEND'); } catch (Horde_Icalendar_Exception $e) { } if (!$endStamp) { try { $duration = $vFb->getAttribute('DURATION'); $endStamp = $startStamp + $duration; } catch (Horde_Icalendar_Exception $e) { $endStamp = $startStamp + 60 * 24 * 3600; } } } $vfb_reply = $registry->call('calendar/getFreeBusy', array($startStamp, $endStamp)); // Find out who we are and update status. $identity = $injector->getInstance('IMP_Identity'); $email = $identity->getFromAddress(); // Build the reply. $msg_headers = new Horde_Mime_Headers(); $vCal = new Horde_Icalendar(); $vCal->setAttribute('PRODID', '-//The Horde Project//' . $msg_headers->getUserAgent() . '//EN'); $vCal->setAttribute('METHOD', 'REPLY'); $vCal->addComponent($vfb_reply); $message = _("Attached is a reply to a calendar request you sent."); $body = new Horde_Mime_Part(); $body->setType('text/plain'); $body->setCharset('UTF-8'); $body->setContents(Horde_String::wrap($message, 76)); $ics = new Horde_Mime_Part(); $ics->setType('text/calendar'); $ics->setCharset('UTF-8'); $ics->setContents($vCal->exportvCalendar()); $ics->setName('icalendar.ics'); $ics->setContentTypeParameter('METHOD', 'REPLY'); $mime = new Horde_Mime_Part(); $mime->addPart($body); $mime->addPart($ics); // Build the reply headers. $msg_headers->addReceivedHeader(array('dns' => $injector->getInstance('Net_DNS2_Resolver'), 'server' => $conf['server']['name'])); $msg_headers->addMessageIdHeader(); $msg_headers->addHeader('Date', date('r')); $msg_headers->addHeader('From', $email); $msg_headers->addHeader('To', $organizerFullEmail); $identity->setDefault($vars->identity); $replyto = $identity->getValue('replyto_addr'); if (!empty($replyto) && !$email->match($replyto)) { $msg_headers->addHeader('Reply-To', $replyto); } $msg_headers->addHeader('Subject', _("Free/Busy Request Response")); // Send the reply. try { $mime->send($organizerEmail, $msg_headers, $injector->getInstance('IMP_Mail')); $notification->push(_("Reply Sent."), 'horde.success'); $result = true; } catch (Exception $e) { $notification->push(sprintf(_("Error sending reply: %s."), $e->getMessage()), 'horde.error'); } } else { $notification->push(_("Invalid Action selected for this component."), 'horde.warning'); } break; case 'nosup': // vFreebusy request. // vFreebusy request. default: $notification->push(_("This action is not supported."), 'horde.warning'); break; } } return $result; }
protected function _getTestPart() { $part = new Horde_Mime_Part(); $part->setType('multipart/mixed'); $part1 = new Horde_Mime_Part(); $part1->setType('text/plain'); $part1->setContents('Test'); $part->addPart($part1); $part2 = new Horde_Mime_Part(); $part2->setType('application/octet-stream'); $part->addPart($part2); $part3 = new Horde_Mime_Part(); $part3->setType('multipart/mixed'); $part->addPart($part3); $part3_1 = new Horde_Mime_Part(); $part3_1->setType('text/plain'); $part3_1->setContents('Test 2'); $part3->addPart($part3_1); $part->buildMimeIds(); return $part; }