1<?php
2/**
3 * @license   http://www.horde.org/licenses/gpl GPLv2
4 *
5 * @copyright 2012-2020 Horde LLC (http://www.horde.org)
6 * @author    Michael J Rubinsky <mrubinsk@horde.org>
7 * @package   ActiveSync
8 */
9
10/**
11 * This class provides all functionality related to parsing and working with
12 * a single IMAP email message when using Horde_Imap_Client.
13 *
14 * Some Mime parsing code taken from Imp_Contents.
15 *
16 * @license   http://www.horde.org/licenses/gpl GPLv2
17 *
18 * @copyright 2012-2020 Horde LLC (http://www.horde.org)
19 * @author    Michael J Rubinsky <mrubinsk@horde.org>
20 * @package   ActiveSync
21 *
22 * @property-read Horde_Imap_Client_Data_Envelope $envelope
23 *     The message envelope.
24 * @property-read array $flags                     The message flags.
25 * @property-read integer $uid                     The message uid.
26 * @property-read Horde_ActiveSync_Mime $basePart  The base message part.
27 */
28class Horde_ActiveSync_Imap_Message
29{
30    const OPTIONS_DECODE_TNEF = "decode_tnef";
31
32    /**
33     * Message data.
34     *
35     * @var Horde_Imap_Client_Data_Fetch
36     */
37    protected $_data;
38
39    /**
40     * Message structure.
41     *
42     * @var Horde_ActiveSync_Mime
43     */
44    protected $_basePart;
45
46    /**
47     * The imap client.
48     *
49     * @var Horde_Imap_Client_Base
50     */
51    protected $_imap;
52
53    /**
54     * Cache if the last body part was encoded or not.
55     *
56     * @var boolean
57     */
58    protected $_lastBodyPartDecode;
59
60    /**
61     * Flag to indicate if this message contains attachments.
62     *
63     * @var boolean
64     */
65    protected $_hasAttachments;
66
67    /**
68     * Local cache of MessageBodyData object.
69     *
70     * @var Horde_ActiveSync_Imap_MessageBodyData
71     */
72    protected $_mbd;
73
74    /**
75     * Additional message options
76     *
77     * @var array @since 2.40.0
78     */
79    protected $_options;
80
81    /**
82     * Flag to indicate the mime map was processed for potential TNEF parts.
83     *
84     * @var boolean
85     */
86    protected $_tnefPrepared = false;
87
88    /**
89     * Constructor
90     *
91     * @param Horde_Imap_Client_Base $imap        The imap client object.
92     * @param Horde_Imap_Client_Mailbox $mbox     The mailbox object.
93     * @param Horde_Imap_Client_Data_Fetch $data  The data returned from a FETCH
94     *                                            must contain at least uid,
95     *                                            structure and flags.
96     * @param array $options                      Additional Options
97     *                                            @Since 2.40.0
98     *  ATTACHEMENT_OPTIONS_DECODE_TNEF  - if true will attempt to decode TNEF
99     *      MIME Parts. If false, will return TNEF data as-is. DEFAULT: True.
100     */
101    public function __construct(
102        Horde_Imap_Client_Base $imap,
103        Horde_Imap_Client_Mailbox $mbox,
104        Horde_Imap_Client_Data_Fetch $data,
105        array $options = array())
106    {
107        $this->_imap = $imap;
108        $this->_mbox = $mbox;
109        $this->_data = $data;
110        $this->_options = array_merge(
111            array(self::OPTIONS_DECODE_TNEF => true),
112            $options
113        );
114
115        $this->_prepareTnef();
116    }
117
118    public function __destruct()
119    {
120        $this->_data = null;
121        $this->_basePart = null;
122    }
123
124    /**
125     * Accessor
126     *
127     * @param  string $property The property.
128     *
129     * @return mixed
130     */
131    public function &__get($property)
132    {
133        switch ($property) {
134        case 'envelope':
135            $e = $this->_data->getEnvelope();
136            return $e;
137        case 'flags':
138            $f = $this->_data->getFlags();
139            return $f;
140        case 'uid':
141            $u = $this->_data->getUid();
142            return $u;
143        case 'basePart':
144            if (empty($this->_basePart)) {
145                $this->_basePart = new Horde_ActiveSync_Mime($this->_data->getStructure());
146            }
147            return $this->_basePart;
148        }
149
150        throw new InvalidArgumentException(sprintf('The property %s of Horde_ActiveSync_Imap_Message does not exist', $property));
151    }
152
153    /**
154     * Return this message's base part headers.
155     *
156     * @return Horde_Mime_Headers  The message headers.
157     */
158    public function getHeaders()
159    {
160        return $this->_data->getHeaderText(0, Horde_Imap_Client_Data_Fetch::HEADER_PARSE);
161    }
162
163    /**
164     * Return nicely formatted text representing the headers to display with
165     * in-line forwarded messages.
166     *
167     * @return string
168     */
169    public function getForwardHeaders()
170    {
171        $tmp = array();
172        $h = $this->getHeaders();
173
174        if (($ob = $h->getValue('date'))) {
175            $tmp[Horde_ActiveSync_Translation::t('Date')] = $ob;
176        }
177
178        if (($ob = strval($h->getOb('from')))) {
179            $tmp[Horde_ActiveSync_Translation::t('From')] = $ob;
180        }
181
182        if (($ob = strval($h->getOb('reply-to')))) {
183            $tmp[Horde_ActiveSync_Translation::t('Reply-To')] = $ob;
184        }
185
186        if (($ob = $h->getValue('subject'))) {
187            $tmp[Horde_ActiveSync_Translation::t('Subject')] = $ob;
188        }
189
190        if (($ob = strval($h->getOb('to')))) {
191            $tmp[Horde_ActiveSync_Translation::t('To')] = $ob;
192        }
193
194        if (($ob = strval($h->getOb('cc')))) {
195            $tmp[Horde_ActiveSync_Translation::t('Cc')] = $ob;
196        }
197
198        $max = max(array_map(array('Horde_String', 'length'), array_keys($tmp))) + 2;
199        $text = '';
200
201        foreach ($tmp as $key => $val) {
202            $text .= Horde_String::pad($key . ': ', $max, ' ', STR_PAD_LEFT) . $val . "\n";
203        }
204
205        return $text;
206    }
207
208    /**
209     * Return the full message text.
210     *
211     * @param boolean $stream  Return data as a stream?
212     *
213     * @return mixed  A string or stream resource.
214     * @throws Horde_ActiveSync_Exception
215     */
216    public function getFullMsg($stream = false)
217    {
218        // First see if we already have it.
219        if ($stream) {
220            $full = new Horde_Stream_Existing(array('stream' => $this->_data->getFullMsg($stream)));
221            $length = $full->length();
222            if (!$length) {
223                $full->close();
224            }
225        } else {
226            $full = $this->_data->getFullMsg(false);
227            $length = strlen($full);
228        }
229        if (!$length) {
230            $query = new Horde_Imap_Client_Fetch_Query();
231            $query->fullText(array('peek' => true));
232            try {
233                $fetch_ret = $this->_imap->fetch(
234                    $this->_mbox,
235                    $query,
236                    array('ids' => new Horde_Imap_Client_Ids(array($this->uid)))
237                );
238            } catch (Horde_Imap_Client_Exception $e) {
239                throw new Horde_ActiveSync_Exception($e);
240            }
241            $data = $fetch_ret[$this->uid];
242            $full = $data->getFullMsg($stream);
243        }
244
245        return $full;
246    }
247
248    /**
249     * Return the message's base Mime part.
250     *
251     * @return Horde_Mime_Part
252     */
253    public function getStructure()
254    {
255        return $this->basePart->base;
256    }
257
258    /**
259     * Returns the main text body of the message suitable for sending over
260     * EAS response.
261     *
262     * @param array $options  An options array containgin:
263     *  - bodyprefs: (array)  Bodypref settings
264     *               DEFAULT: none (No bodyprefs used).
265     *  - mimesupport: (integer)  Indicates if MIME is supported or not.
266     *                  Possible values: 0 - Not supported 1 - Only S/MIME or
267     *                  2 - All MIME.
268     *                  DEFAULT: 0 (No MIME support)
269     *  - protocolversion: (float)  The EAS protocol we are supporting.
270     *                     DEFAULT 2.5
271     *
272     * @return array  An array of one or both of 'plain' and 'html' content.
273     *
274     * @throws Horde_ActiveSync_Exception, Horde_Exception_NotFound
275     * @deprecated - no longer used and will be removed in Horde 6.
276     */
277    public function getMessageBodyData(array $options = array())
278    {
279        return $this->getMessageBodyDataObject($options)->toArray();
280    }
281
282    /**
283     * Returns the main text body of the message suitable for sending over
284     * EAS response.
285     *
286     * @param array $options  An options array containgin:
287     *  - bodyprefs: (array)  Bodypref settings
288     *               DEFAULT: none (No bodyprefs used).
289     *  - mimesupport: (integer)  Indicates if MIME is supported or not.
290     *                  Possible values: 0 - Not supported 1 - Only S/MIME or
291     *                  2 - All MIME.
292     *                  DEFAULT: 0 (No MIME support)
293     *  - protocolversion: (float)  The EAS protocol we are supporting.
294     *                     DEFAULT 2.5
295     *
296     * @return Horde_ActiveSync_Imap_MessageBodyData  The result.
297     *
298     * @throws Horde_ActiveSync_Exception, Horde_Exception_NotFound
299     */
300    public function getMessageBodyDataObject(array $options = array())
301    {
302        if (empty($this->_mbd)) {
303           $this->_mbd = new Horde_ActiveSync_Imap_MessageBodyData(
304                array(
305                    'imap' => $this->_imap,
306                    'mbox' => $this->_mbox,
307                    'uid' => $this->uid,
308                    'mime' => $this->basePart),
309                $options
310            );
311        }
312
313        return $this->_mbd;
314    }
315
316    /**
317     * Return an array of Horde_ActiveSync_Message_Attachment objects for
318     * the current message.
319     *
320     * @param float $version  The EAS protocol version this is for.
321     *
322     * @return array  An array of Horde_ActiveSync_Message_Attachment objects.
323     */
324    public function getAttachments($version)
325    {
326        $ret = array();
327        $iterator = new Horde_ActiveSync_Mime_Iterator($this->_basePart->base);
328        foreach ($iterator as $part) {
329            $type = $part->getType();
330            $id = $part->getMimeId();
331            if ($this->isAttachment($id, $type)) {
332                $mime_part = $this->getMimePart($id, array('nocontents' => true));
333                $ret[] = $this->_buildEasAttachmentFromMime($id, $mime_part, $version);
334                $mime_part = null;
335            }
336        }
337
338        return $ret;
339    }
340
341    /**
342     * Build an appropriate attachment object from the given mime part.
343     *
344     * @param integer $id                  The mime id for the part
345     * @param Horde_Mime_Part  $mime_part  The mime part.
346     * @param float $version               The EAS version.
347     *
348     * @return Horde_ActiveSync_Message_AirSyncBaseAttachment |
349     *         Horde_ActiveSync_Message_Attachment
350     */
351    protected function _buildEasAttachmentFromMime($id, Horde_Mime_Part $mime_part, $version)
352    {
353        if ($version > Horde_ActiveSync::VERSION_TWOFIVE) {
354            $atc = Horde_ActiveSync::messageFactory('AirSyncBaseAttachment');
355            $atc->contentid = $mime_part->getContentId();
356            $atc->isinline = $mime_part->getDisposition() == 'inline';
357        } else {
358            $atc = Horde_ActiveSync::messageFactory('Attachment');
359            $atc->attoid = $mime_part->getContentId();
360        }
361        $atc->attsize = intval($mime_part->getBytes(true));
362        $atc->attname = $this->_mbox . ':' . $this->uid . ':' . $id;
363        $atc->displayname = $this->getPartName($mime_part, true);
364        $atc->attmethod = in_array($mime_part->getType(), array('message/disposition-notification'))
365            ? Horde_ActiveSync_Message_AirSyncBaseAttachment::ATT_TYPE_EMBEDDED
366            : Horde_ActiveSync_Message_AirSyncBaseAttachment::ATT_TYPE_NORMAL;
367
368        if ($mime_part->getType() == 'message/rfc822') {
369            $atc->displayname .= ".eml";
370        }
371
372        return $atc;
373    }
374
375    /**
376     * Convert a TNEF attachment into a multipart/mixed part.
377     *
378     * @param  integer|Horde_Mime_part $data  Either a mime part id or a
379     *                                        Horde_Mime_Part object containing
380     *                                        the TNEF attachment.
381     *
382     * @return Horde_Mime_Part|boolean  The multipart/mixed MIME part containing
383     *                                  any attachment data we can decode or
384     *                                  false on failure.
385     */
386    protected function _decodeTnefData($data)
387    {
388        $wrapper = new Horde_Mime_Part();
389        $wrapper->setType('multipart/mixed');
390
391        if (!($data instanceof Horde_Mime_Part)) {
392            $mime_part = $this->getMimePart($data);
393        } else {
394            $mime_part = $data;
395        }
396        $wrapper->setName($mime_part->getName());
397        $wrapper->setMimeId($mime_part->getMimeId());
398
399        $tnef_parser = Horde_Compress::factory('Tnef');
400        try {
401            $tnef_data = $tnef_parser->decompress($mime_part->getContents());
402        } catch (Horde_Compress_Exception $e) {
403            return false;
404        }
405        if (!count($tnef_data)) {
406            return false;
407        }
408
409        foreach ($tnef_data as $data) {
410            $tmp_part = new Horde_Mime_Part();
411            $tmp_part->setName($data['name']);
412            $tmp_part->setDescription($data['name']);
413            $tmp_part->setContents($data['stream']);
414
415            $type = $data['type'] . '/' . $data['subtype'];
416            if (in_array($type, array('application/octet-stream', 'application/base64'))) {
417                $type = Horde_Mime_Magic::filenameToMIME($data['name']);
418            }
419            $tmp_part->setType($type);
420            $wrapper->addPart($tmp_part);
421        }
422
423        $wrapper->buildMimeIds();
424
425        return $wrapper;
426    }
427
428    /**
429     * Return an array of mime parts for each message attachment.
430     *
431     * @return array An array of Horde_Mime_Part objects.
432     */
433    public function getAttachmentsMimeParts()
434    {
435        $mime_parts = array();
436        $map = $this->basePart->contentTypeMap();
437        foreach ($map as $id => $type) {
438            if ($this->isAttachment($id, $type)) {
439                $mpart = $this->getMimePart($id);
440                if ($mpart->getType() == 'text/calendar') {
441                    $mpart->setDisposition('inline');
442                }
443                $mime_parts[] = $mpart;
444                $mpart = null;
445            }
446        }
447
448        return $mime_parts;
449    }
450
451    /**
452     * Check the mime structure for any TNEF data, and if neccessary attempt
453     * to decode data and inject back into the base mime part.
454     */
455    protected function _prepareTnef()
456    {
457        if ($this->_tnefPrepared) {
458            return;
459        }
460        $map = $this->basePart->contentTypeMap();
461        foreach ($map as $id => $type) {
462            if ($type == 'application/ms-tnef' &&
463                !empty($this->_options[self::OPTIONS_DECODE_TNEF])) {
464
465                $mpart = $this->getMimePart($id);
466                $tnef_part = $this->_decodeTnefData($mpart);
467                if ($tnef_part) {
468                    $this->basePart->alterPart($id, $tnef_part);
469                }
470            }
471        }
472        $this->basePart->buildMimeIds(null, false);
473        $this->_tnefPrepared = true;
474    }
475
476    /**
477     * Fetch a part of a MIME message.
478     *
479     * @param integer $id     The MIME index of the part requested.
480     * @param array $options  Additional options:
481     *   - length: (integer) If set, only download this many bytes of the
482     *             bodypart from the server.
483     *             DEFAULT: All data is retrieved.
484     *   - nocontents: (boolean) If true, don't add the contents to the part
485     *                 DEFAULT: Contents are added to the part
486     *
487     * @return Horde_Mime_Part  The raw MIME part asked for.
488     */
489    public function getMimePart($id, array $options = array())
490    {
491        $part = $this->basePart->getPart($id);
492        if ($part &&
493            (strcasecmp($part->getCharset(), 'ISO-8859-1') === 0)) {
494            $part->setCharset('windows-1252');
495        }
496
497        if (!empty($id) &&
498            !is_null($part) &&
499            substr($id, -2) != '.0' &&
500            empty($options['nocontents']) &&
501            !$part->getContents(array('stream' => true))) {
502
503            try {
504                $body = $this->getBodyPart(
505                    $id,
506                    array(
507                        'decode' => true,
508                        'length' => empty($options['length']) ? null : $options['length'],
509                        'stream' => true)
510                );
511                $part->setContents($body, array('encoding' => $this->_lastBodyPartDecode, 'usestream' => true));
512            } catch (Horde_ActiveSync_Exception $e) {
513            }
514        }
515
516        return $part;
517    }
518
519    /**
520     * Return the descriptive part label, making sure it is not empty.
521     *
522     * @param Horde_Mime_Part $part  The MIME Part object.
523     * @param boolean $use_descrip   Use description? If false, uses name.
524     *
525     * @return string  The part label (non-empty).
526     */
527    public function getPartName(Horde_Mime_Part $part, $use_descrip = false)
528    {
529        $name = $use_descrip
530            ? $part->getDescription(true)
531            : $part->getName(true);
532
533        if ($name) {
534            return $name;
535        }
536
537        switch ($ptype = $part->getPrimaryType()) {
538        case 'multipart':
539            if (($part->getSubType() == 'related') &&
540                ($view_id = $part->getMetaData('viewable_part')) &&
541                ($viewable = $this->getMimePart($view_id, array('nocontents' => true)))) {
542                return $this->getPartName($viewable, $use_descrip);
543            }
544            /* Fall-through. */
545
546        case 'application':
547        case 'model':
548            $ptype = $part->getSubType();
549            break;
550        }
551
552        switch ($ptype) {
553        case 'audio':
554            return Horde_ActiveSync_Translation::t('Audio part');
555
556        case 'image':
557            return Horde_ActiveSync_Translation::t('Image part');
558
559        case 'message':
560        case Horde_Mime_Part::UNKNOWN:
561            return Horde_ActiveSync_Translation::t('Message part');
562
563        case 'multipart':
564            return Horde_ActiveSync_Translation::t('Multipart part');
565
566        case 'text':
567            return Horde_ActiveSync_Translation::t('Text part');
568
569        case 'video':
570            return Horde_ActiveSync_Translation::t('Video part');
571
572        default:
573            // Attempt to translate this type, if possible. Odds are that
574            // it won't appear in the dictionary though.
575            return sprintf(Horde_ActiveSync_Translation::t('%s part'), _(Horde_String::ucfirst($ptype)));
576        }
577    }
578
579    /**
580     * Gets the raw text for one section of the message.
581     *
582     * @param integer $id     The ID of the MIME part.
583     * @param array $options  Additional options:
584     *   - decode: (boolean) Attempt to decode the bodypart on the remote
585     *             server. If successful, sets self::$_lastBodyPartDecode to
586     *             the content-type of the decoded data.
587     *             DEFAULT: No
588     *   - length: (integer) If set, only download this many bytes of the
589     *             bodypart from the server.
590     *             DEFAULT: All data is retrieved.
591     *   - mimeheaders: (boolean) Include the MIME headers also?
592     *                  DEFAULT: No
593     *   - stream: (boolean) If true, return a stream.
594     *             DEFAULT: No
595     *
596     * @return mixed  The text of the part or a stream resource if 'stream'
597     *                is true.
598     * @throws  Horde_ActiveSync_Exception when a bodypart cannot be found.
599     * @todo Simplify by removing 'mimeheaders' parameter (not used).
600     */
601    public function getBodyPart($id, $options)
602    {
603        $options = array_merge(
604            array(
605                'decode' => false,
606                'mimeheaders' => false,
607                'stream' => false),
608            $options);
609        $this->_lastBodyPartDecode = null;
610        $query = new Horde_Imap_Client_Fetch_Query();
611        if (!isset($options['length']) || !empty($options['length'])) {
612            $bodypart_params = array(
613                'decode' => true,
614                'peek' => true
615            );
616
617            if (isset($options['length'])) {
618                $bodypart_params['start'] = 0;
619                $bodypart_params['length'] = $options['length'];
620            }
621
622            $query->bodyPart($id, $bodypart_params);
623        }
624
625        if (!empty($options['mimeheaders'])) {
626            $query->mimeHeader($id, array(
627                'peek' => true
628            ));
629        }
630
631        $fetch_res = $this->_imap->fetch(
632            $this->_mbox,
633            $query,
634            array('ids' => new Horde_Imap_Client_Ids(array($this->uid)))
635        );
636
637        if (empty($fetch_res[$this->uid])) {
638            throw new Horde_ActiveSync_Exception('Message not found!');
639        }
640
641        if (empty($options['mimeheaders'])) {
642            $this->_lastBodyPartDecode = $fetch_res[$this->uid]->getBodyPartDecode($id);
643            return $fetch_res[$this->uid]->getBodyPart($id, $options['stream']);
644        } elseif (empty($options['stream'])) {
645            return $fetch_res[$this->uid]->getMimeHeader($id) . $fetch_res[$this->uid]->getBodyPart($id);
646        } else {
647            $swrapper = new Horde_Support_CombineStream(
648                array(
649                    $fetch_res[$this->uid]->getMimeHeader($id, Horde_Imap_Client_Data_Fetch::HEADER_STREAM),
650                    $fetch_res[$this->uid]->getBodyPart($id, true))
651            );
652
653            return $swrapper->fopen();
654        }
655    }
656
657    /**
658     * Return the To addresses from this message.
659     *
660     * @return array  An array containing arrays of 'to' and 'displayto'
661     *                addresses. @since 2.37.1, ensures the text is UTF8.
662     */
663    public function getToAddresses()
664    {
665        $to = $this->envelope->to;
666        $dtos = $tos = array();
667        foreach ($to->raw_addresses as $e) {
668            $tos[] = Horde_ActiveSync_Utils::ensureUtf8($e->bare_address, 'UTF-8');
669            $dtos[] = Horde_ActiveSync_Utils::ensureUtf8($e->label, 'UTF-8');
670        }
671
672        return array('to' => $tos, 'displayto' => $dtos);
673    }
674
675    /**
676     * Return the CC addresses for this message.
677     *
678     * @return string  The Cc address string.
679     * @throws Horde_ActiveSync_Exception @since 2.27.0
680     */
681    public function getCc()
682    {
683        try {
684            $cc = new Horde_Mail_Rfc822_List($this->envelope->cc->addresses);
685        } catch (Horde_Mail_Exception $e) {
686            throw new Horde_ActiveSync_Exception($e);
687        }
688        return $cc->writeAddress();
689    }
690
691    /**
692     * Return the ReplyTo Address
693     *
694     * @return string
695     * @throws Horde_ActiveSync_Exception @since 2.27.0
696     */
697    public function getReplyTo()
698    {
699        $r = $this->envelope->reply_to->addresses;
700        try {
701            $a = new Horde_Mail_Rfc822_Address(current($r));
702        } catch (Horde_Mail_Exception $e) {
703            throw new Horde_ActiveSync_Exception($e);
704        }
705
706        return $a->writeAddress(false);
707    }
708
709    /**
710     * Return the message's From: address.
711     *
712     * @return string  The From address of this message.
713     * @throws Horde_ActiveSync_Exception @since 2.27.0
714     */
715    public function getFromAddress()
716    {
717        $from = $this->envelope->from->addresses;
718        try {
719            $a = new Horde_Mail_Rfc822_Address(current($from));
720        } catch (Horde_ActiveSync_Exception $e) {
721            throw new Horde_ActiveSync_Exception($e);
722        }
723
724        return $a->writeAddress(false);
725    }
726
727    /**
728     * Return the message subject.
729     *
730     * @return string  The subject.
731     */
732    public function getSubject()
733    {
734        return $this->envelope->subject;
735    }
736
737    /**
738     * Return the message date.
739     *
740     * @return Horde_Date  The messages's envelope date.
741     */
742    public function getDate()
743    {
744        return new Horde_Date((string)$this->envelope->date);
745    }
746
747    /**
748     * Get a message flag
749     *
750     * @param string $flag  The flag to search for.
751     *
752     * @return boolean
753     */
754    public function getFlag($flag)
755    {
756        return (array_search($flag, $this->flags) !== false)
757            ? 1
758            : 0;
759    }
760
761    /**
762     * Return all message flags.
763     *
764     * @return array  An array of message flags.
765     * @since 2.17.0
766     */
767    public function getFlags()
768    {
769        return $this->flags;
770    }
771
772    /**
773     * Return this message's content map
774     *
775     * @return array  The content map, with mime ids as keys and content type
776     *                as values.
777     */
778    public function contentTypeMap()
779    {
780        return $this->basePart->contentTypeMap();
781    }
782
783    /**
784     * Determines if a MIME type is an attachment.
785     * For our purposes, an attachment is any MIME part that can be
786     * downloaded by itself (i.e. all the data needed to view the part is
787     * contained within the download data).
788     *
789     * @param string $id         The MIME Id for the part we are checking.
790     * @param string $mime_type  The MIME type.
791     *
792     * @return boolean  True if an attachment.
793     * @deprecated Will be removed in 3.0 (Only used in self::hasAttachments call).
794     */
795    public function isAttachment($id, $mime_type)
796    {
797        return $this->basePart->isAttachment($id, $mime_type);
798    }
799
800    /**
801     * Return the MIME part of the iCalendar attachment, if available.
802     *
803     * @return mixed  The mime part, if present, false otherwise.
804     */
805    public function hasiCalendar()
806    {
807        if ($id = $this->basePart->hasiCalendar()) {
808            // May already have downloaded the part.
809            $part = $this->basePart->base->getPart($id);
810            if (!$part->getContents(array('stream' => true))) {
811                return $this->getMimePart($id);
812            }
813            return $part;
814        }
815
816        return false;
817    }
818
819    /**
820     * Return the hasAttachments flag
821     *
822     * @return boolean
823     */
824    public function hasAttachments()
825    {
826        return $this->basePart->hasAttachments();
827    }
828
829    /**
830     * Return the S/MIME signature status of this message (RFC2633)
831     *
832     * @param Horde_Mime_Part $message  A mime part to check. If omitted, use
833     *                                  self::$_message.
834     *
835     * @return boolean True if message is S/MIME signed, false otherwise.
836     */
837    public function isSigned(Horde_Mime_Part $message = null)
838    {
839        if (!empty($message)) {
840            $message = new Horde_ActiveSync_Mime($message);
841            return $message->isSigned();
842        }
843
844        return $this->basePart->isSigned();
845    }
846
847    /**
848     * Return the S/MIME encryption status of this message (RFC2633)
849     *
850     * @param Horde_Mime_Part $message  A mime part to check. If omitted, use
851     *                                  self::$_message.
852     *
853     * @return boolean True if message is S/MIME signed or encrypted,
854     *                 false otherwise.
855     */
856    public function isEncrypted(Horde_Mime_Part $message = null)
857    {
858        if (!empty($message)) {
859            $message = new Horde_ActiveSync_Mime($message);
860            return $message->isEncrypted();
861        }
862
863        return $this->basePart->isEncrypted();
864    }
865
866}
867