001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.geronimo.javamail.store.imap;
019    
020    import java.io.BufferedReader;
021    import java.io.ByteArrayInputStream;
022    import java.io.ByteArrayOutputStream;
023    import java.io.IOException;
024    import java.io.InputStream;
025    import java.io.InputStreamReader;
026    import java.io.OutputStream;
027    import java.io.UnsupportedEncodingException;
028    import java.util.Date;
029    import java.util.Enumeration;
030    import java.util.List;
031    
032    import javax.activation.DataHandler;
033    
034    import javax.mail.Address; 
035    import javax.mail.FetchProfile;
036    import javax.mail.Flags;
037    import javax.mail.Folder;
038    import javax.mail.Header;
039    import javax.mail.IllegalWriteException;
040    import javax.mail.Message;
041    import javax.mail.MessagingException;
042    import javax.mail.MessageRemovedException;
043    import javax.mail.Session;
044    import javax.mail.Store;
045    import javax.mail.UIDFolder;
046    import javax.mail.event.MessageChangedEvent;
047    
048    import javax.mail.internet.InternetAddress;
049    import javax.mail.internet.InternetHeaders;
050    import javax.mail.internet.MailDateFormat;     
051    import javax.mail.internet.MimeMessage;
052    import javax.mail.internet.MimeUtility;
053    
054    import org.apache.geronimo.javamail.store.imap.connection.IMAPBody; 
055    import org.apache.geronimo.javamail.store.imap.connection.IMAPBodyStructure; 
056    import org.apache.geronimo.javamail.store.imap.connection.IMAPConnection; 
057    import org.apache.geronimo.javamail.store.imap.connection.IMAPEnvelope;
058    import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchDataItem;
059    import org.apache.geronimo.javamail.store.imap.connection.IMAPFetchResponse;
060    import org.apache.geronimo.javamail.store.imap.connection.IMAPInternalDate;
061    import org.apache.geronimo.javamail.store.imap.connection.IMAPInternetHeader;
062    import org.apache.geronimo.javamail.store.imap.connection.IMAPMessageSize;
063    import org.apache.geronimo.javamail.store.imap.connection.IMAPUid;
064    import org.apache.geronimo.javamail.util.MailConnection;
065    
066    /**
067     * IMAP implementation of javax.mail.internet.MimeMessage
068     *
069     * Only the most basic information is given and
070     * Message objects created here is a light-weight reference to the actual Message
071     * As per the JavaMail spec items from the actual message will get filled up on demand
072     *
073     * If some other items are obtained from the server as a result of one call, then the other
074     * details are also processed and filled in. For ex if RETR is called then header information
075     * will also be processed in addition to the content
076     *
077     * @version $Rev: 739377 $ $Date: 2009-01-30 13:57:39 -0500 (Fri, 30 Jan 2009) $
078     */
079    public class IMAPMessage extends MimeMessage {
080        
081        private static final byte[] CRLF = "\r\n".getBytes();
082        
083        // the Store we're stored in (which manages the connection and other stuff).
084        protected IMAPStore store;
085    
086        // the IMAP server sequence number (potentially updated during the life of this message object).
087        protected int sequenceNumber;
088        // the IMAP uid value;
089        protected long uid = -1;
090        // the section identifier.  This is only really used for nested messages.  The toplevel version  
091        // will be null, and each nested message will set the appropriate part identifier 
092        protected String section; 
093        // the loaded message envelope (delayed until needed)
094        protected IMAPEnvelope envelope;
095        // the body structure information (also lazy loaded).
096        protected IMAPBodyStructure bodyStructure;
097        // the IMAP INTERNALDATE value.
098        protected Date receivedDate;
099        // the size item, which is maintained separately from the body structure 
100        // as it can be retrieved without getting the body structure
101        protected int size; 
102        // turned on once we've requested the entire header set. 
103        protected boolean allHeadersRetrieved = false; 
104        // singleton date formatter for this class.
105        static protected MailDateFormat dateFormat = new MailDateFormat();
106    
107    
108        /**
109         * Contruct an IMAPMessage instance.
110         * 
111         * @param folder   The hosting folder for the message.
112         * @param store    The Store owning the article (and folder).
113         * @param msgnum   The article message number.  This is assigned by the Folder, and is unique
114         *                 for each message in the folder.  The message numbers are only valid
115         *                 as long as the Folder is open.
116         * @param sequenceNumber The IMAP server manages messages by sequence number, which is subject to
117         *                 change whenever messages are expunged.  This is the server retrieval number
118         *                 of the message, which needs to be synchronized with status updates
119         *                 sent from the server.
120         * 
121         * @exception MessagingException
122         */
123            IMAPMessage(IMAPFolder folder, IMAPStore store, int msgnum, int sequenceNumber) {
124                    super(folder, msgnum);
125            this.sequenceNumber = sequenceNumber;
126                    this.store = store;
127            // The default constructor creates an empty Flags item.  We need to clear this out so we 
128            // know if the flags need to be fetched from the server when requested.  
129            flags = null;
130            // make sure this is a totally fresh set of headers.  We'll fill things in as we retrieve them.
131            headers = new InternetHeaders();
132            }
133    
134    
135        /**
136         * Override for the Message class setExpunged() method to allow
137         * us to do additional cleanup for expunged messages.
138         *
139         * @param value  The new expunge setting.
140         */
141        public void setExpunged(boolean value) {
142            // super class handles most of the details
143            super.setExpunged(value);
144            // if we're now expunged, this removes us from the server message sequencing scheme, so
145            // we need to invalidate the sequence number.
146            if (isExpunged()) {
147                sequenceNumber = -1;
148            }
149        }
150        
151    
152        /**
153         * Return a copy the flags associated with this message.
154         *
155         * @return a copy of the flags for this message
156         * @throws MessagingException if there was a problem accessing the Store
157         */
158        public synchronized Flags getFlags() throws MessagingException {
159            // load the flags, if needed 
160            loadFlags(); 
161            return super.getFlags(); 
162        }
163    
164    
165        /**
166         * Check whether the supplied flag is set.
167         * The default implementation checks the flags returned by {@link #getFlags()}.
168         *
169         * @param flag the flags to check for
170         * @return true if the flags is set
171         * @throws MessagingException if there was a problem accessing the Store
172         */
173        public synchronized boolean isSet(Flags.Flag flag) throws MessagingException {
174            // load the flags, if needed 
175            loadFlags(); 
176            return super.isSet(flag); 
177        }
178    
179        /**
180         * Set or clear a flag value.
181         *
182         * @param flags  The set of flags to effect.
183         * @param set    The value to set the flag to (true or false).
184         *
185         * @exception MessagingException
186         */
187        public synchronized void setFlags(Flags flag, boolean set) throws MessagingException {
188            // make sure this is in a valid state.
189            checkValidity();
190            
191            // we need to ensure that we're the only ones with access to the folder's 
192            // message cache any time we need to talk to the server.  This needs to be 
193            // held until after we release the connection so that any pending EXPUNGE 
194            // untagged responses are processed before the next time the folder connection is 
195            // used. 
196            synchronized (folder) {
197                IMAPConnection connection = getConnection();
198    
199                try {
200                    // set the flags for this item and update the 
201                    // internal state with the new values returned from the 
202                    // server. 
203                    flags = connection.setFlags(sequenceNumber, flag, set); 
204                } finally {
205                    releaseConnection(connection); 
206                }
207            }
208        }
209    
210    
211        /**
212         * Return an InputStream instance for accessing the 
213         * message content.
214         * 
215         * @return An InputStream instance for accessing the content
216         *         (body) of the message.
217         * @exception MessagingException
218         * @see javax.mail.internet.MimeMessage#getContentStream()
219         */
220            protected InputStream getContentStream() throws MessagingException {
221    
222            // no content loaded yet?
223            if (content == null) {
224                // make sure we're still valid
225                checkValidity();
226                // make sure the content is fully loaded
227                loadContent();
228            }
229    
230            // allow the super class to handle creating it from the loaded content.
231            return super.getContentStream();
232            }
233    
234    
235        /**
236         * Write out the byte data to the provided output stream.
237         *
238         * @param out    The target stream.
239         *
240         * @exception IOException
241         * @exception MessagingException
242         */
243        public void writeTo(OutputStream out) throws IOException, MessagingException {                      
244            // no content loaded yet?
245            if (content == null) {
246                // make sure we're still valid
247                checkValidity();
248                // make sure the content is fully loaded
249                loadContent();
250            }
251            
252            loadHeaders();
253            
254            Enumeration e = headers.getAllHeaderLines();
255            while(e.hasMoreElements()) {
256                String line = (String)e.nextElement();
257                out.write(line.getBytes());
258                out.write(CRLF);
259            }
260            out.write(CRLF);
261            out.write(CRLF);
262            out.write(content);
263        }
264        
265            /******************************************************************
266             * Following is a set of methods that deal with information in the 
267             * envelope.  These methods ensure the enveloper is loaded and   
268         * retrieve the information.                         
269             ********************************************************************/
270         
271    
272        /**
273         * Get the message "From" addresses.  This looks first at the
274         * "From" headers, and no "From" header is found, the "Sender"
275         * header is checked.  Returns null if not found.
276         *
277         * @return An array of addresses identifying the message from target.  Returns
278         *         null if this is not resolveable from the headers.
279         * @exception MessagingException
280         */
281        public Address[] getFrom() throws MessagingException {
282            // make sure we've retrieved the envelope information.
283            loadEnvelope();
284            // make sure we return a copy of the array so this can't be changed. 
285            Address[] addresses = envelope.from; 
286            if (addresses == null) {
287                return null;
288            }
289            return (Address[])addresses.clone(); 
290        }
291        
292    
293        /**
294         * Return the "Sender" header as an address.
295         *
296         * @return the "Sender" header as an address, or null if not present
297         * @throws MessagingException if there was a problem parsing the header
298         */
299        public Address getSender() throws MessagingException {
300            // make sure we've retrieved the envelope information.
301            loadEnvelope();
302            // make sure we return a copy of the array so this can't be changed. 
303            Address[] addresses = envelope.sender; 
304            if (addresses == null) {
305                return null;
306            }
307            // There's only a single sender, despite IMAP potentially returning a list
308            return addresses[0]; 
309        }
310    
311        /**
312         * Gets the recipients by type.  Returns null if there are no
313         * headers of the specified type.  Acceptable RecipientTypes are:
314         *
315         *   javax.mail.Message.RecipientType.TO
316         *   javax.mail.Message.RecipientType.CC
317         *   javax.mail.Message.RecipientType.BCC
318         *   javax.mail.internet.MimeMessage.RecipientType.NEWSGROUPS
319         *
320         * @param type   The message RecipientType identifier.
321         *
322         * @return The array of addresses for the specified recipient types.
323         * @exception MessagingException
324         */
325        public Address[] getRecipients(Message.RecipientType type) throws MessagingException {
326            // make sure we've retrieved the envelope information.
327            loadEnvelope();
328            Address[] addresses = null; 
329            
330            if (type == Message.RecipientType.TO) {
331                addresses = envelope.to; 
332            }
333            else if (type == Message.RecipientType.CC) {
334                addresses = envelope.cc; 
335            }
336            else if (type == Message.RecipientType.BCC) {
337                addresses = envelope.bcc; 
338            }
339            else {
340                // this could be a newsgroup type, which will tickle the message headers. 
341                return super.getRecipients(type);
342            }
343            // make sure we return a copy of the array so this can't be changed. 
344            if (addresses == null) {
345                return null;
346            }
347            return (Address[])addresses.clone(); 
348        }
349    
350        /**
351         * Get the ReplyTo address information.  The headers are parsed
352         * using the "mail.mime.address.strict" setting.  If the "Reply-To" header does
353         * not have any addresses, then the value of the "From" field is used.
354         *
355         * @return An array of addresses obtained from parsing the header.
356         * @exception MessagingException
357         */
358        public Address[] getReplyTo() throws MessagingException {
359            // make sure we've retrieved the envelope information.
360            loadEnvelope();
361            // make sure we return a copy of the array so this can't be changed. 
362            Address[] addresses = envelope.replyTo; 
363            if (addresses == null) {
364                return null;
365            }
366            return (Address[])addresses.clone(); 
367        }
368    
369        /**
370         * Returns the value of the "Subject" header.  If the subject
371         * is encoded as an RFC 2047 value, the value is decoded before
372         * return.  If decoding fails, the raw string value is
373         * returned.
374         *
375         * @return The String value of the subject field.
376         * @exception MessagingException
377         */
378        public String getSubject() throws MessagingException {
379            // make sure we've retrieved the envelope information.
380            loadEnvelope();
381            
382            if (envelope.subject == null) {
383                return null; 
384            }
385            // the subject could be encoded.  If there is a decoding error, 
386            // return the raw subject string. 
387            try {
388                return MimeUtility.decodeText(envelope.subject); 
389            } catch (UnsupportedEncodingException e) {
390                return envelope.subject; 
391            }
392        }
393    
394        /**
395         * Get the value of the "Date" header field.  Returns null if
396         * if the field is absent or the date is not in a parseable format.
397         *
398         * @return A Date object parsed according to RFC 822.
399         * @exception MessagingException
400         */
401        public Date getSentDate() throws MessagingException {
402            // make sure we've retrieved the envelope information.
403            loadEnvelope();
404            // just return that directly 
405            return envelope.date; 
406        }
407    
408    
409        /**
410         * Get the message received date.
411         *
412         * @return Always returns the formatted INTERNALDATE, if available.
413         * @exception MessagingException
414         */
415        public Date getReceivedDate() throws MessagingException {
416            loadEnvelope();
417            return receivedDate; 
418        }
419    
420    
421        /**
422         * Retrieve the size of the message content.  The content will
423         * be retrieved from the server, if necessary.
424         *
425         * @return The size of the content.
426         * @exception MessagingException
427         */
428            public int getSize() throws MessagingException {
429            // make sure we've retrieved the envelope information.  We load the 
430            // size when we retrieve that. 
431            loadEnvelope();
432            return size;                          
433            }
434        
435    
436        /**
437         * Get a line count for the IMAP message.  This is potentially
438         * stored in the Lines article header.  If not there, we return
439         * a default of -1.
440         *
441         * @return The header line count estimate, or -1 if not retrieveable.
442         * @exception MessagingException
443         */
444        public int getLineCount() throws MessagingException {
445            loadBodyStructure();
446            return bodyStructure.lines; 
447        }
448    
449        /**
450         * Return the IMAP in reply to information (retrieved with the
451         * ENVELOPE).
452         *
453         * @return The in reply to String value, if available.
454         * @exception MessagingException
455         */
456        public String getInReplyTo() throws MessagingException {
457            loadEnvelope();
458            return envelope.inReplyTo;
459        }
460    
461        /**
462         * Returns the current content type (defined in the "Content-Type"
463         * header.  If not available, "text/plain" is the default.
464         *
465         * @return The String name of the message content type.
466         * @exception MessagingException
467         */
468        public String getContentType() throws MessagingException {
469            loadBodyStructure();
470            return bodyStructure.mimeType.toString(); 
471        }
472    
473    
474        /**
475         * Tests to see if this message has a mime-type match with the
476         * given type name.
477         *
478         * @param type   The tested type name.
479         *
480         * @return If this is a type match on the primary and secondare portion of the types.
481         * @exception MessagingException
482         */
483        public boolean isMimeType(String type) throws MessagingException {
484            loadBodyStructure();
485            return bodyStructure.mimeType.match(type); 
486        }
487    
488        /**
489         * Retrieve the message "Content-Disposition" header field.
490         * This value represents how the part should be represented to
491         * the user.
492         *
493         * @return The string value of the Content-Disposition field.
494         * @exception MessagingException
495         */
496        public String getDisposition() throws MessagingException {
497            loadBodyStructure();
498            if (bodyStructure.disposition != null) {
499                return bodyStructure.disposition.getDisposition(); 
500            }
501            return null; 
502        }
503    
504        /**
505         * Decode the Content-Transfer-Encoding header to determine
506         * the transfer encoding type.
507         *
508         * @return The string name of the required encoding.
509         * @exception MessagingException
510         */
511        public String getEncoding() throws MessagingException {
512            loadBodyStructure();
513            return bodyStructure.transferEncoding; 
514        }
515    
516        /**
517         * Retrieve the value of the "Content-ID" header.  Returns null
518         * if the header does not exist.
519         *
520         * @return The current header value or null.
521         * @exception MessagingException
522         */
523        public String getContentID() throws MessagingException {
524            loadBodyStructure();
525            return bodyStructure.contentID;         
526        }
527    
528        public String getContentMD5() throws MessagingException {
529            loadBodyStructure();
530            return bodyStructure.md5Hash;         
531        }
532        
533        
534        public String getDescription() throws MessagingException {
535            loadBodyStructure();
536            
537            if (bodyStructure.contentDescription == null) {
538                return null; 
539            }
540            // the subject could be encoded.  If there is a decoding error, 
541            // return the raw subject string. 
542            try {
543                return MimeUtility.decodeText(bodyStructure.contentDescription); 
544            } catch (UnsupportedEncodingException e) {
545                return bodyStructure.contentDescription;
546            }
547        }
548    
549        /**
550         * Return the content languages associated with this 
551         * message.
552         * 
553         * @return 
554         * @exception MessagingException
555         */
556        public String[] getContentLanguage() throws MessagingException {
557            loadBodyStructure();
558            
559            if (!bodyStructure.languages.isEmpty()) {
560                return (String[])bodyStructure.languages.toArray(new String[bodyStructure.languages.size()]); 
561            }
562            return null; 
563        }
564    
565        public String getMessageID() throws MessagingException {
566            loadEnvelope();
567            return envelope.messageID; 
568        }
569        
570        public void setFrom(Address address) throws MessagingException {
571            throw new IllegalWriteException("IMAP messages are read-only");
572        }
573        
574        public void addFrom(Address[] address) throws MessagingException {
575            throw new IllegalWriteException("IMAP messages are read-only");
576        }
577        
578        public void setSender(Address address) throws MessagingException {
579            throw new IllegalWriteException("IMAP messages are read-only");
580        }
581        
582        public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException {
583            throw new IllegalWriteException("IMAP messages are read-only");
584        }
585        
586        public void setRecipients(Message.RecipientType type, String address) throws MessagingException {
587            throw new IllegalWriteException("IMAP messages are read-only");
588        }
589        
590        public void addRecipients(Message.RecipientType type, Address[] address) throws MessagingException {
591            throw new IllegalWriteException("IMAP messages are read-only");
592        }
593        
594        public void setReplyTo(Address[] address) throws MessagingException {
595            throw new IllegalWriteException("IMAP messages are read-only");
596        }
597        
598        public void setSubject(String subject) throws MessagingException {
599            throw new IllegalWriteException("IMAP messages are read-only");
600        }
601        
602        public void setSubject(String subject, String charset) throws MessagingException {
603            throw new IllegalWriteException("IMAP messages are read-only");
604        }
605        
606        public void setSentDate(Date sent) throws MessagingException {
607            throw new IllegalWriteException("IMAP messages are read-only");
608        }
609        
610        public void setDisposition(String disposition) throws MessagingException {
611            throw new IllegalWriteException("IMAP messages are read-only");
612        }
613        
614        public void setContentID(String cid) throws MessagingException {
615            throw new IllegalWriteException("IMAP messages are read-only");
616        }
617    
618        public void setContentMD5(String md5) throws MessagingException {
619            throw new IllegalWriteException("IMAP messages are read-only");
620        }
621    
622        public void setDescription(String description) throws MessagingException {
623            throw new IllegalWriteException("IMAP messages are read-only");
624        }
625        
626        public void setDescription(String description, String charset) throws MessagingException {
627            throw new IllegalWriteException("IMAP messages are read-only");
628        }
629    
630        public void setContentLanguage(String[] languages) throws MessagingException {
631            throw new IllegalWriteException("IMAP messages are read-only");
632        }
633        
634    
635            /******************************************************************
636             * Following is a set of methods that deal with headers
637             * These methods are just overrides on the superclass methods to
638         * allow lazy loading of the header information.
639             ********************************************************************/
640    
641            public String[] getHeader(String name) throws MessagingException {
642            loadHeaders();
643                    return headers.getHeader(name);
644            }
645    
646            public String getHeader(String name, String delimiter) throws MessagingException {
647            loadHeaders();
648                    return headers.getHeader(name, delimiter);
649            }
650    
651            public Enumeration getAllHeaders() throws MessagingException {
652            loadHeaders();
653                    return headers.getAllHeaders();
654            }
655    
656            public Enumeration getMatchingHeaders(String[] names)  throws MessagingException {
657            loadHeaders();
658                    return headers.getMatchingHeaders(names);
659            }
660    
661            public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException {
662            loadHeaders();
663                    return headers.getNonMatchingHeaders(names);
664            }
665    
666            public Enumeration getAllHeaderLines() throws MessagingException {
667            loadHeaders();
668                    return headers.getAllHeaderLines();
669            }
670    
671            public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException {
672            loadHeaders();
673                    return headers.getMatchingHeaderLines(names);
674            }
675    
676            public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException {
677            loadHeaders();
678                    return headers.getNonMatchingHeaderLines(names);
679            }
680    
681        // the following are overrides for header modification methods.  These messages are read only,
682        // so the headers cannot be modified.
683        public void addHeader(String name, String value) throws MessagingException {
684            throw new IllegalWriteException("IMAP messages are read-only");
685        }
686    
687        public void setHeader(String name, String value) throws MessagingException {
688            throw new IllegalWriteException("IMAP messages are read-only");
689        }
690    
691    
692        public void removeHeader(String name) throws MessagingException {
693            throw new IllegalWriteException("IMAP messages are read-only");
694        }
695    
696        public void addHeaderLine(String line) throws MessagingException {
697            throw new IllegalWriteException("IMAP messages are read-only");
698        }
699    
700            /**
701             * We cannot modify these messages
702             */
703            public void saveChanges() throws MessagingException {
704                    throw new IllegalWriteException("IMAP messages are read-only");
705            }
706    
707    
708        /**
709         * Utility method for synchronizing IMAP envelope information and
710         * the message headers.
711         *
712         * @param header    The target header name.
713         * @param addresses The update addresses.
714         */
715        protected void updateHeader(String header, InternetAddress[] addresses) throws MessagingException {
716            if (addresses != null) {
717                headers.addHeader(header, InternetAddress.toString(addresses));
718            }
719        }
720    
721        /**
722         * Utility method for synchronizing IMAP envelope information and
723         * the message headers.
724         *
725         * @param header    The target header name.
726         * @param address   The update address.
727         */
728        protected void updateHeader(String header, Address address) throws MessagingException {
729            if (address != null) {
730                headers.setHeader(header, address.toString());
731            }
732        }
733    
734        /**
735         * Utility method for synchronizing IMAP envelope information and
736         * the message headers.
737         *
738         * @param header    The target header name.
739         * @param value     The update value.
740         */
741        protected void updateHeader(String header, String value) throws MessagingException {
742            if (value != null) {
743                headers.setHeader(header, value);
744            }
745        }
746    
747    
748        /**
749         * Create the DataHandler object for this message.
750         *
751         * @return The DataHandler object that processes the content set for this
752         *         message.
753         * @exception MessagingException
754         */
755        public synchronized DataHandler getDataHandler() throws MessagingException {
756            // check the validity and make sure we have the body structure information. 
757            checkValidity();
758            loadBodyStructure();
759            if (dh == null) {
760                // are we working with a multipart message here?
761                if (bodyStructure.isMultipart()) {
762                    dh = new DataHandler(new IMAPMultipartDataSource(this, this, section, bodyStructure));
763                    return dh;
764                }
765                else if (bodyStructure.isAttachedMessage()) {
766                    dh = new DataHandler(new IMAPAttachedMessage(this, section, bodyStructure.nestedEnvelope, bodyStructure.nestedBody), 
767                         bodyStructure.mimeType.toString()); 
768                    return dh;
769                }
770            }
771    
772            // single part messages get handled the normal way.
773            return super.getDataHandler();
774        }
775    
776        public void setDataHandler(DataHandler content) throws MessagingException {
777            throw new IllegalWriteException("IMAP body parts are read-only");
778        }
779    
780        /**
781         * Update the message headers from an input stream.
782         *
783         * @param in     The InputStream source for the header information.
784         *
785         * @exception MessagingException
786         */
787        public void updateHeaders(InputStream in) throws MessagingException {
788            // wrap a stream around the reply data and read as headers.
789            headers = new InternetHeaders(in);
790            allHeadersRetrieved = true;
791        }
792        
793        /**
794         * Load the flag set for this message from the server. 
795         * 
796         * @exception MessagingeException
797         */
798        public void loadFlags() throws MessagingException {
799            // make sure this is in a valid state.
800            checkValidity();
801            // if the flags are already loaded, nothing to do 
802            if (flags != null) {
803                return; 
804            }
805            // we need to ensure that we're the only ones with access to the folder's 
806            // message cache any time we need to talk to the server.  This needs to be 
807            // held until after we release the connection so that any pending EXPUNGE 
808            // untagged responses are processed before the next time the folder connection is 
809            // used. 
810            synchronized (folder) {
811                IMAPConnection connection = getConnection();
812    
813                try {
814                    // fetch the flags for this item. 
815                    flags = connection.fetchFlags(sequenceNumber); 
816                } finally {
817                    releaseConnection(connection); 
818                }
819            }
820        }
821    
822    
823        /**
824         * Retrieve the message raw message headers from the IMAP server, synchronizing with the existing header set.
825         *
826         * @exception MessagingException
827         */
828        protected synchronized void loadHeaders() throws MessagingException {
829            // don't retrieve if already loaded.
830            if (allHeadersRetrieved) {
831                return;
832            }
833    
834            // make sure this is in a valid state.
835            checkValidity();
836            // we need to ensure that we're the only ones with access to the folder's 
837            // message cache any time we need to talk to the server.  This needs to be 
838            // held until after we release the connection so that any pending EXPUNGE 
839            // untagged responses are processed before the next time the folder connection is 
840            // used. 
841            synchronized (folder) {
842                IMAPConnection connection = getConnection();
843    
844                try {
845                    // get the headers and set 
846                    headers = connection.fetchHeaders(sequenceNumber, section);
847                    // we have the entire header set, not just a subset. 
848                    allHeadersRetrieved = true;                                               
849                } finally {
850                    releaseConnection(connection); 
851                }
852            }
853        }
854    
855    
856        /**
857         * Retrieve the message envelope from the IMAP server, synchronizing the headers with the
858         * information.
859         *
860         * @exception MessagingException
861         */                                
862        protected synchronized void loadEnvelope() throws MessagingException {
863            // don't retrieve if already loaded.
864            if (envelope != null) {
865                return;
866            }
867    
868            // make sure this is in a valid state.
869            checkValidity();
870            // we need to ensure that we're the only ones with access to the folder's 
871            // message cache any time we need to talk to the server.  This needs to be 
872            // held until after we release the connection so that any pending EXPUNGE 
873            // untagged responses are processed before the next time the folder connection is 
874            // used. 
875            synchronized (folder) {
876                IMAPConnection connection = getConnection();
877                try {
878                    // fetch the envelope information for this
879                    List fetches = connection.fetchEnvelope(sequenceNumber);
880                    // now process all of the fetch responses before releasing the folder lock.  
881                    // it's possible that an unsolicited update on another thread might try to 
882                    // make an update, causing a potential deadlock. 
883                    for (int i = 0; i < fetches.size(); i++) {
884                        // get the returned data items from each of the fetch responses
885                        // and process. 
886                        IMAPFetchResponse fetch = (IMAPFetchResponse)fetches.get(i);
887                        // update the internal info 
888                        updateMessageInformation(fetch); 
889                    }
890                } finally {
891                    releaseConnection(connection); 
892                }
893            }
894        }
895    
896    
897        /**
898         * Retrieve the message envelope from the IMAP server, synchronizing the headers with the
899         * information.
900         *
901         * @exception MessagingException
902         */
903        protected synchronized void updateEnvelope(IMAPEnvelope envelope) throws MessagingException {
904            // set the envelope item 
905            this.envelope = envelope; 
906    
907            // copy header type information from the envelope into the headers.
908            updateHeader("From", envelope.from);
909            if (envelope.sender != null) {
910                // we can only have a single sender, even though the envelope theoretically supports more.
911                updateHeader("Sender", envelope.sender[0]);
912            }
913            updateHeader("To", envelope.to);
914            updateHeader("Cc", envelope.cc);
915            updateHeader("Bcc", envelope.bcc);
916            updateHeader("Reply-To", envelope.replyTo);
917            // NB:  This is already in encoded form, if needed.
918            updateHeader("Subject", envelope.subject);
919            updateHeader("Message-ID", envelope.messageID);
920        }
921    
922    
923        /**
924         * Retrieve the BODYSTRUCTURE information from the IMAP server.
925         *
926         * @exception MessagingException
927         */
928        protected synchronized void loadBodyStructure() throws MessagingException {
929            // don't retrieve if already loaded.
930            if (bodyStructure != null) {
931                return;
932            }
933    
934            // make sure this is in a valid state.
935            checkValidity();
936            // we need to ensure that we're the only ones with access to the folder's 
937            // message cache any time we need to talk to the server.  This needs to be 
938            // held until after we release the connection so that any pending EXPUNGE 
939            // untagged responses are processed before the next time the folder connection is 
940            // used. 
941            synchronized (folder) {
942                IMAPConnection connection = getConnection();
943                try {
944                    // fetch the envelope information for this
945                    bodyStructure = connection.fetchBodyStructure(sequenceNumber);
946                    // go update all of the information 
947                } finally {
948                    releaseConnection(connection); 
949                }
950                
951                // update this before we release the folder lock so we can avoid 
952                // deadlock. 
953                updateBodyStructure(bodyStructure); 
954            }
955        }
956    
957    
958        /**
959         * Update the BODYSTRUCTURE information from the IMAP server.
960         *
961         * @exception MessagingException
962         */
963        protected synchronized void updateBodyStructure(IMAPBodyStructure structure) throws MessagingException {
964            // save the reference. 
965            bodyStructure = structure; 
966            // now update various headers with the information from the body structure 
967    
968            // now update header information with the body structure data.
969            if (bodyStructure.lines != -1) {
970                updateHeader("Lines", Integer.toString(bodyStructure.lines));
971            }
972    
973            // languages are a little more complicated
974            if (bodyStructure.languages != null) {
975                // this is a duplicate of what happens in the super class, but 
976                // the superclass methods call setHeader(), which we override and 
977                // throw an exception for.  We need to set the headers ourselves. 
978                if (bodyStructure.languages.size() == 1) {
979                    updateHeader("Content-Language", (String)bodyStructure.languages.get(0));
980                }
981                else {
982                    StringBuffer buf = new StringBuffer(bodyStructure.languages.size() * 20);
983                    buf.append(bodyStructure.languages.get(0));
984                    for (int i = 1; i < bodyStructure.languages.size(); i++) {
985                        buf.append(',').append(bodyStructure.languages.get(i));
986                    }
987                    updateHeader("Content-Language", buf.toString());
988                }
989            }
990    
991            updateHeader("Content-Type", bodyStructure.mimeType.toString());
992            if (bodyStructure.disposition != null) {
993                updateHeader("Content-Disposition", bodyStructure.disposition.toString());
994            }
995    
996            updateHeader("Content-Transfer-Encoding", bodyStructure.transferEncoding);
997            updateHeader("Content-ID", bodyStructure.contentID);
998            // NB:  This is already in encoded form, if needed.
999            updateHeader("Content-Description", bodyStructure.contentDescription);
1000        }
1001    
1002    
1003        /**
1004         * Load the message content into the Message object.
1005         *
1006         * @exception MessagingException
1007         */
1008        protected void loadContent() throws MessagingException {
1009            // if we've loaded this already, just return
1010            if (content != null) {
1011                return;
1012            }
1013            
1014            // we need to ensure that we're the only ones with access to the folder's 
1015            // message cache any time we need to talk to the server.  This needs to be 
1016            // held until after we release the connection so that any pending EXPUNGE 
1017            // untagged responses are processed before the next time the folder connection is 
1018            // used. 
1019            synchronized (folder) {
1020                IMAPConnection connection = getConnection();
1021                try {
1022                    // load the content from the server. 
1023                    content = connection.fetchContent(getSequenceNumber(), section); 
1024                } finally {
1025                    releaseConnection(connection); 
1026                }
1027            }
1028        }
1029    
1030        
1031        /**
1032         * Retrieve the sequence number assigned to this message.
1033         *
1034         * @return The messages assigned sequence number.  This maps back to the server's assigned number for
1035         * this message.
1036         */
1037        int getSequenceNumber() {
1038            return sequenceNumber;
1039        }
1040        
1041        /**
1042         * Set the sequence number for the message.  This 
1043         * is updated whenever messages get expunged from 
1044         * the folder. 
1045         * 
1046         * @param s      The new sequence number.
1047         */
1048        void setSequenceNumber(int s) {
1049            sequenceNumber = s; 
1050        }
1051    
1052    
1053        /**
1054         * Retrieve the message UID value.
1055         *
1056         * @return The assigned UID value, if we have the information.
1057         */
1058        long getUID() {
1059            return uid;
1060        }
1061    
1062        /**
1063         * Set the message UID value.
1064         *
1065         * @param uid    The new UID value.
1066         */
1067        void setUID(long uid) {
1068            this.uid = uid;
1069        }
1070    
1071        
1072        /**
1073         * get the current connection pool attached to the folder.  We need
1074         * to do this dynamically, to A) ensure we're only accessing an
1075         * currently open folder, and B) to make sure we're using the
1076         * correct connection attached to the folder.
1077         *
1078         * @return A connection attached to the hosting folder.
1079         */
1080        protected IMAPConnection getConnection() throws MessagingException {
1081            // the folder owns everything.
1082            return ((IMAPFolder)folder).getMessageConnection();
1083        }
1084        
1085        /**
1086         * Release the connection back to the Folder after performing an operation 
1087         * that requires a connection.
1088         * 
1089         * @param connection The previously acquired connection.
1090         */
1091        protected void releaseConnection(IMAPConnection connection) throws MessagingException {
1092            // the folder owns everything.
1093            ((IMAPFolder)folder).releaseMessageConnection(connection);
1094        }
1095    
1096    
1097        /**
1098         * Check the validity of the current message.  This ensures that
1099         * A) the folder is currently open, B) that the message has not
1100         * been expunged (after getting the latest status from the server).
1101         *
1102         * @exception MessagingException
1103         */
1104        protected void checkValidity() throws MessagingException {
1105            checkValidity(false); 
1106        }
1107    
1108    
1109        /**
1110         * Check the validity of the current message.  This ensures that
1111         * A) the folder is currently open, B) that the message has not
1112         * been expunged (after getting the latest status from the server).
1113         *
1114         * @exception MessagingException
1115         */
1116        protected void checkValidity(boolean update) throws MessagingException {
1117            // we need to ensure that we're the only ones with access to the folder's 
1118            // message cache any time we need to talk to the server.  This needs to be 
1119            // held until after we release the connection so that any pending EXPUNGE 
1120            // untagged responses are processed before the next time the folder connection is 
1121            // used. 
1122            if (update) {
1123                synchronized (folder) {
1124                    // have the connection update the folder status.  This might result in this message
1125                    // changing its state to expunged.  It might also result in an exception if the
1126                    // folder has been closed.
1127                    IMAPConnection connection = getConnection(); 
1128    
1129                    try {
1130                        connection.updateMailboxStatus();
1131                    } finally {
1132                        // this will force any expunged messages to be processed before we release 
1133                        // the lock. 
1134                        releaseConnection(connection); 
1135                    }
1136                }
1137            }
1138    
1139            // now see if we've been expunged, this is a bad op on the message.
1140            if (isExpunged()) {
1141                throw new MessageRemovedException("Illegal opertion on a deleted message");
1142            }
1143        }
1144        
1145        
1146        /**
1147         * Evaluate whether this message requires any of the information 
1148         * in a FetchProfile to be fetched from the server.  If the messages 
1149         * already contains the information in the profile, it returns false.
1150         * This allows IMAPFolder to optimize fetch() requests to just the 
1151         * messages that are missing any of the requested information.
1152         * 
1153         * NOTE:  If any of the items in the profile are missing, then this 
1154         * message will be updated with ALL of the items.  
1155         * 
1156         * @param profile The FetchProfile indicating the information that should be prefetched.
1157         * 
1158         * @return true if any of the profile information requires fetching.  false if this 
1159         *         message already contains the given information.
1160         */
1161        protected boolean evaluateFetch(FetchProfile profile) {
1162            // the fetch profile can contain a number of different item types.  Validate
1163            // whether we need any of these and return true on the first mismatch. 
1164            
1165            // the UID is a common fetch request, put it first. 
1166            if (profile.contains(UIDFolder.FetchProfileItem.UID) && uid == -1) {
1167                return true; 
1168            }
1169            if (profile.contains(FetchProfile.Item.ENVELOPE) && envelope == null) {
1170                return true; 
1171            }
1172            if (profile.contains(FetchProfile.Item.FLAGS) && flags == null) {
1173                return true; 
1174            }
1175            if (profile.contains(FetchProfile.Item.CONTENT_INFO) && bodyStructure == null) {
1176                return true; 
1177            }
1178            // The following profile items are our implementation of items that the 
1179            // Sun IMAPFolder implementation supports.  
1180            if (profile.contains(IMAPFolder.FetchProfileItem.HEADERS) && !allHeadersRetrieved) {
1181                return true; 
1182            }
1183            if (profile.contains(IMAPFolder.FetchProfileItem.SIZE) && bodyStructure.bodySize < 0) {
1184                return true; 
1185            }
1186            // last bit after checking each of the information types is to see if 
1187            // particular headers have been requested and whether those are on the 
1188            // set we do have loaded. 
1189            String [] requestedHeaders = profile.getHeaderNames(); 
1190             
1191            // ok, any missing header in the list is enough to force us to request the 
1192            // information. 
1193            for (int i = 0; i < requestedHeaders.length; i++) {
1194                if (headers.getHeader(requestedHeaders[i]) == null) {
1195                    return true; 
1196                }
1197            }
1198            // this message, at least, does not need anything fetched. 
1199            return false; 
1200        }
1201        
1202        /**
1203         * Update a message instance with information retrieved via an IMAP FETCH 
1204         * command.  The command response for this message may contain multiple pieces
1205         * that we need to process.
1206         * 
1207         * @param response The response line, which may contain multiple data items.
1208         * 
1209         * @exception MessagingException
1210         */
1211        void updateMessageInformation(IMAPFetchResponse response) throws MessagingException {
1212            // get the list of data items associated with this response.  We can have 
1213            // a large number of items returned in a single update. 
1214            List items = response.getDataItems(); 
1215            
1216            for (int i = 0; i < items.size(); i++) {
1217                IMAPFetchDataItem item = (IMAPFetchDataItem)items.get(i); 
1218                
1219                switch (item.getType()) {
1220                    // if the envelope has been requested, we'll end up with all of these items. 
1221                    case IMAPFetchDataItem.ENVELOPE:
1222                        // update the envelope and map the envelope items into the headers. 
1223                        updateEnvelope((IMAPEnvelope)item);
1224                        break;
1225                    case IMAPFetchDataItem.INTERNALDATE:
1226                        receivedDate = ((IMAPInternalDate)item).getDate();;
1227                        break;
1228                    case IMAPFetchDataItem.SIZE:
1229                        size = ((IMAPMessageSize)item).size;      
1230                        break;
1231                    case IMAPFetchDataItem.UID:
1232                        uid = ((IMAPUid)item).uid;       
1233                        // make sure the folder knows about the UID update. 
1234                        ((IMAPFolder)folder).addToUidCache(new Long(uid), this); 
1235                        break; 
1236                    case IMAPFetchDataItem.BODYSTRUCTURE: 
1237                        updateBodyStructure((IMAPBodyStructure)item); 
1238                        break; 
1239                        // a partial or full header update 
1240                    case IMAPFetchDataItem.HEADER:
1241                    {
1242                        // if we've fetched the complete set, then replace what we have 
1243                        IMAPInternetHeader h = (IMAPInternetHeader)item; 
1244                        if (h.isComplete()) {
1245                            // we've got a complete header set now. 
1246                            this.headers = h.headers;     
1247                            allHeadersRetrieved = true; 
1248                        }
1249                        else {
1250                            // need to merge the requested headers in with 
1251                            // our existing set.  We need to be careful, since we 
1252                            // don't want to add duplicates. 
1253                            mergeHeaders(h.headers); 
1254                        }
1255                    }
1256                    default:
1257                }
1258            }
1259        }
1260        
1261        
1262        /**
1263         * Merge a subset of the requested headers with our existing partial set. 
1264         * The new set will contain all headers requested from the server, plus 
1265         * any of our existing headers that were not included in the retrieved set.
1266         * 
1267         * @param newHeaders The retrieved set of headers.
1268         */
1269        protected synchronized void mergeHeaders(InternetHeaders newHeaders) {
1270            // This is sort of tricky to manage.  The input headers object is a fresh set  
1271            // retrieved from the server, but it's a subset of the headers.  Our existing set
1272            // might not be complete, but it may contain duplicates of information in the 
1273            // retrieved set, plus headers that are not in the retrieved set.  To keep from 
1274            // adding duplicates, we'll only add headers that are not in the retrieved set to 
1275            // that set.   
1276            
1277            // start by running through the list of headers 
1278            Enumeration e = headers.getAllHeaders(); 
1279            
1280            while (e.hasMoreElements()) {
1281                Header header = (Header)e.nextElement(); 
1282                // if there are no headers with this name in the new set, then 
1283                // we can add this.  Note that to add the header, we need to 
1284                // retrieve all instances by this name and add them as a unit.  
1285                // When we hit one of the duplicates again with the enumeration, 
1286                // we'll skip it then because the merge target will have everything. 
1287                if (newHeaders.getHeader(header.getName()) == null) {
1288                    // get all occurrences of this name and stuff them into the 
1289                    // new list 
1290                    String name = header.getName(); 
1291                    String[] a = headers.getHeader(name); 
1292                    for (int i = 0; i < a.length; i++) {
1293                        newHeaders.addHeader(name, a[i]); 
1294                    }
1295                }
1296            }
1297            // and replace the current header set
1298            headers = newHeaders; 
1299        }
1300    }