Skip to content

Package: IMAPMessage$FetchProfileCondition

IMAPMessage$FetchProfileCondition

nameinstructionbranchcomplexitylinemethod
IMAPMessage.FetchProfileCondition(FetchProfile, FetchItem[])
M: 124 C: 0
0%
M: 22 C: 0
0%
M: 12 C: 0
0%
M: 34 C: 0
0%
M: 1 C: 0
0%
test(IMAPMessage)
M: 120 C: 0
0%
M: 48 C: 0
0%
M: 25 C: 0
0%
M: 27 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package org.eclipse.angus.mail.imap;
18:
19: import jakarta.activation.DataHandler;
20: import jakarta.mail.Address;
21: import jakarta.mail.FetchProfile;
22: import jakarta.mail.Flags;
23: import jakarta.mail.FolderClosedException;
24: import jakarta.mail.Header;
25: import jakarta.mail.IllegalWriteException;
26: import jakarta.mail.Message;
27: import jakarta.mail.MessageRemovedException;
28: import jakarta.mail.MessagingException;
29: import jakarta.mail.Session;
30: import jakarta.mail.UIDFolder;
31: import jakarta.mail.internet.ContentType;
32: import jakarta.mail.internet.InternetAddress;
33: import jakarta.mail.internet.InternetHeaders;
34: import jakarta.mail.internet.MimeMessage;
35: import jakarta.mail.internet.MimeUtility;
36: import org.eclipse.angus.mail.iap.ConnectionException;
37: import org.eclipse.angus.mail.iap.ProtocolException;
38: import org.eclipse.angus.mail.iap.Response;
39: import org.eclipse.angus.mail.imap.protocol.BODY;
40: import org.eclipse.angus.mail.imap.protocol.BODYSTRUCTURE;
41: import org.eclipse.angus.mail.imap.protocol.ENVELOPE;
42: import org.eclipse.angus.mail.imap.protocol.FetchItem;
43: import org.eclipse.angus.mail.imap.protocol.FetchResponse;
44: import org.eclipse.angus.mail.imap.protocol.IMAPProtocol;
45: import org.eclipse.angus.mail.imap.protocol.INTERNALDATE;
46: import org.eclipse.angus.mail.imap.protocol.Item;
47: import org.eclipse.angus.mail.imap.protocol.MODSEQ;
48: import org.eclipse.angus.mail.imap.protocol.RFC822DATA;
49: import org.eclipse.angus.mail.imap.protocol.RFC822SIZE;
50: import org.eclipse.angus.mail.imap.protocol.UID;
51: import org.eclipse.angus.mail.util.ReadableMime;
52:
53: import java.io.ByteArrayInputStream;
54: import java.io.IOException;
55: import java.io.InputStream;
56: import java.io.OutputStream;
57: import java.io.UnsupportedEncodingException;
58: import java.util.Date;
59: import java.util.Enumeration;
60: import java.util.HashMap;
61: import java.util.HashSet;
62: import java.util.Hashtable;
63: import java.util.Iterator;
64: import java.util.Locale;
65: import java.util.Map;
66: import java.util.Set;
67:
68: /**
69: * This class implements an IMAPMessage object. <p>
70: *
71: * An IMAPMessage object starts out as a light-weight object. It gets
72: * filled-in incrementally when a request is made for some item. Or
73: * when a prefetch is done using the FetchProfile. <p>
74: *
75: * An IMAPMessage has a messageNumber and a sequenceNumber. The
76: * messageNumber is its index into its containing folder's messageCache.
77: * The sequenceNumber is its IMAP sequence-number.
78: *
79: * @author John Mani
80: * @author Bill Shannon
81: */
82: /*
83: * The lock hierarchy is that the lock on the IMAPMessage object, if
84: * it's acquired at all, must be acquired before the message cache lock.
85: * The IMAPMessage lock protects the message flags, sort of.
86: *
87: * XXX - I'm not convinced that all fields of IMAPMessage are properly
88: * protected by locks.
89: */
90:
91: public class IMAPMessage extends MimeMessage implements ReadableMime {
92: protected BODYSTRUCTURE bs; // BODYSTRUCTURE
93: protected ENVELOPE envelope; // ENVELOPE
94:
95: /**
96: * A map of the extension FETCH items. In addition to saving the
97: * data in this map, an entry in this map indicates that we *have*
98: * the data, and so it doesn't need to be fetched again. The map
99: * is created only when needed, to avoid significantly increasing
100: * the effective size of an IMAPMessage object.
101: *
102: * @since JavaMail 1.4.6
103: */
104: protected Map<String, Object> items; // Map<String,Object>
105:
106: private Date receivedDate; // INTERNALDATE
107: private long size = -1; // RFC822.SIZE
108:
109: private Boolean peek; // use BODY.PEEK when fetching content?
110:
111: // this message's IMAP UID
112: private volatile long uid = -1;
113:
114: // this message's IMAP MODSEQ - RFC 4551 CONDSTORE
115: private volatile long modseq = -1;
116:
117: // this message's IMAP sectionId (null for toplevel message,
118: //         non-null for a nested message)
119: protected String sectionId;
120:
121: // processed values
122: private String type; // Content-Type (with params)
123: private String subject; // decoded (Unicode) subject
124: private String description; // decoded (Unicode) desc
125:
126: // Indicates that we've loaded *all* headers for this message
127: private volatile boolean headersLoaded = false;
128:
129: // Indicates that we've cached the body of this message
130: private volatile boolean bodyLoaded = false;
131:
132: /* Hashtable of names of headers we've loaded from the server.
133: * Used in isHeaderLoaded() and getHeaderLoaded() to keep track
134: * of those headers we've attempted to load from the server. We
135: * need this table of names to avoid multiple attempts at loading
136: * headers that don't exist for a particular message.
137: *
138: * Could this somehow be included in the InternetHeaders object ??
139: */
140: private Hashtable<String, String> loadedHeaders
141: = new Hashtable<>(1);
142:
143: // This is our Envelope
144: static final String EnvelopeCmd = "ENVELOPE INTERNALDATE RFC822.SIZE";
145:
146: /**
147: * Constructor.
148: *
149: * @param folder the folder containing this message
150: * @param msgnum the message sequence number
151: */
152: protected IMAPMessage(IMAPFolder folder, int msgnum) {
153: super(folder, msgnum);
154: flags = null;
155: }
156:
157: /**
158: * Constructor, for use by IMAPNestedMessage.
159: *
160: * @param session the Session
161: */
162: protected IMAPMessage(Session session) {
163: super(session);
164: }
165:
166: /**
167: * Get this message's folder's protocol connection.
168: * Throws FolderClosedException, if the protocol connection
169: * is not available.
170: *
171: * ASSERT: Must hold the messageCacheLock.
172: *
173: * @throws ProtocolException for protocol errors
174: * @return the IMAPProtocol object for the containing folder
175: * @exception FolderClosedException if the folder is closed
176: */
177: protected IMAPProtocol getProtocol()
178: throws ProtocolException, FolderClosedException {
179: ((IMAPFolder) folder).waitIfIdle();
180: IMAPProtocol p = ((IMAPFolder) folder).protocol;
181: if (p == null)
182: throw new FolderClosedException(folder);
183: else
184: return p;
185: }
186:
187: /*
188: * Is this an IMAP4 REV1 server?
189: */
190: protected boolean isREV1() throws FolderClosedException {
191: // access the folder's protocol object without waiting
192: // for IDLE to complete
193: IMAPProtocol p = ((IMAPFolder) folder).protocol;
194: if (p == null)
195: throw new FolderClosedException(folder);
196: else
197: return p.isREV1();
198: }
199:
200: /**
201: * Get the messageCacheLock, associated with this Message's
202: * Folder.
203: *
204: * @return the message cache lock object
205: */
206: protected Object getMessageCacheLock() {
207: return ((IMAPFolder) folder).messageCacheLock;
208: }
209:
210: /**
211: * Get this message's IMAP sequence number.
212: *
213: * ASSERT: This method must be called only when holding the
214: * messageCacheLock.
215: *
216: * @return the message sequence number
217: */
218: protected int getSequenceNumber() {
219: return ((IMAPFolder) folder).messageCache.seqnumOf(getMessageNumber());
220: }
221:
222: /**
223: * Wrapper around the protected method Message.setMessageNumber() to
224: * make that method accessible to IMAPFolder.
225: */
226: @Override
227: protected void setMessageNumber(int msgnum) {
228: super.setMessageNumber(msgnum);
229: }
230:
231: /**
232: * Return the UID for this message.
233: * Returns -1 if not known; use UIDFolder.getUID() in this case.
234: *
235: * @return the UID
236: * @see jakarta.mail.UIDFolder#getUID
237: */
238: protected long getUID() {
239: return uid;
240: }
241:
242: protected void setUID(long uid) {
243: this.uid = uid;
244: }
245:
246: /**
247: * Return the modification sequence number (MODSEQ) for this message.
248: * Returns -1 if not known.
249: *
250: * @return the modification sequence number
251: * @exception MessagingException for failures
252: * @see "RFC 4551"
253: * @since JavaMail 1.5.1
254: */
255: public synchronized long getModSeq() throws MessagingException {
256: if (modseq != -1)
257: return modseq;
258:
259: synchronized (getMessageCacheLock()) { // Acquire Lock
260: try {
261: IMAPProtocol p = getProtocol();
262: checkExpunged(); // insure that message is not expunged
263: MODSEQ ms = p.fetchMODSEQ(getSequenceNumber());
264:
265: if (ms != null)
266: modseq = ms.modseq;
267: } catch (ConnectionException cex) {
268: throw new FolderClosedException(folder, cex.getMessage());
269: } catch (ProtocolException pex) {
270: throw new MessagingException(pex.getMessage(), pex);
271: }
272: }
273: return modseq;
274: }
275:
276: long _getModSeq() {
277: return modseq;
278: }
279:
280: void setModSeq(long modseq) {
281: this.modseq = modseq;
282: }
283:
284: // expose to MessageCache
285: @Override
286: protected void setExpunged(boolean set) {
287: super.setExpunged(set);
288: }
289:
290: // Convenience routine
291: protected void checkExpunged() throws MessageRemovedException {
292: if (expunged)
293: throw new MessageRemovedException();
294: }
295:
296: /**
297: * Do a NOOP to force any untagged EXPUNGE responses
298: * and then check if this message is expunged.
299: *
300: * @exception MessageRemovedException if the message has been removed
301: * @exception FolderClosedException if the folder has been closed
302: */
303: protected void forceCheckExpunged()
304: throws MessageRemovedException, FolderClosedException {
305: synchronized (getMessageCacheLock()) {
306: try {
307: getProtocol().noop();
308: } catch (ConnectionException cex) {
309: throw new FolderClosedException(folder, cex.getMessage());
310: } catch (ProtocolException pex) {
311: // ignore it
312: }
313: }
314: if (expunged)
315: throw new MessageRemovedException();
316: }
317:
318: // Return the block size for FETCH requests
319: // MUST be overridden by IMAPNestedMessage
320: protected int getFetchBlockSize() {
321: return ((IMAPStore) folder.getStore()).getFetchBlockSize();
322: }
323:
324: // Should we ignore the size in the BODYSTRUCTURE?
325: // MUST be overridden by IMAPNestedMessage
326: protected boolean ignoreBodyStructureSize() {
327: return ((IMAPStore) folder.getStore()).ignoreBodyStructureSize();
328: }
329:
330: /**
331: * Get the "From" attribute.
332: */
333: @Override
334: public Address[] getFrom() throws MessagingException {
335: checkExpunged();
336: if (bodyLoaded)
337: return super.getFrom();
338: loadEnvelope();
339: InternetAddress[] a = envelope.from;
340: /*
341: * Per RFC 2822, the From header is required, and thus the IMAP
342: * spec also requires that it be present, but we know that in
343: * practice it is often missing. Some servers fill in the
344: * From field with the Sender field in this case, but at least
345: * Exchange 2007 does not. Use the same fallback strategy used
346: * by MimeMessage.
347: */
348: if (a == null || a.length == 0)
349: a = envelope.sender;
350: return aaclone(a);
351: }
352:
353: @Override
354: public void setFrom(Address address) throws MessagingException {
355: throw new IllegalWriteException("IMAPMessage is read-only");
356: }
357:
358: @Override
359: public void addFrom(Address[] addresses) throws MessagingException {
360: throw new IllegalWriteException("IMAPMessage is read-only");
361: }
362:
363: /**
364: * Get the "Sender" attribute.
365: */
366: @Override
367: public Address getSender() throws MessagingException {
368: checkExpunged();
369: if (bodyLoaded)
370: return super.getSender();
371: loadEnvelope();
372: if (envelope.sender != null && envelope.sender.length > 0)
373: return (envelope.sender)[0]; // there can be only one sender
374: else
375: return null;
376: }
377:
378:
379: @Override
380: public void setSender(Address address) throws MessagingException {
381: throw new IllegalWriteException("IMAPMessage is read-only");
382: }
383:
384: /**
385: * Get the desired Recipient type.
386: */
387: @Override
388: public Address[] getRecipients(Message.RecipientType type)
389: throws MessagingException {
390: checkExpunged();
391: if (bodyLoaded)
392: return super.getRecipients(type);
393: loadEnvelope();
394:
395: if (type == Message.RecipientType.TO)
396: return aaclone(envelope.to);
397: else if (type == Message.RecipientType.CC)
398: return aaclone(envelope.cc);
399: else if (type == Message.RecipientType.BCC)
400: return aaclone(envelope.bcc);
401: else
402: return super.getRecipients(type);
403: }
404:
405: @Override
406: public void setRecipients(Message.RecipientType type, Address[] addresses)
407: throws MessagingException {
408: throw new IllegalWriteException("IMAPMessage is read-only");
409: }
410:
411: @Override
412: public void addRecipients(Message.RecipientType type, Address[] addresses)
413: throws MessagingException {
414: throw new IllegalWriteException("IMAPMessage is read-only");
415: }
416:
417: /**
418: * Get the ReplyTo addresses.
419: */
420: @Override
421: public Address[] getReplyTo() throws MessagingException {
422: checkExpunged();
423: if (bodyLoaded)
424: return super.getReplyTo();
425: loadEnvelope();
426: /*
427: * The IMAP spec requires that the Reply-To field never be
428: * null, but at least Exchange 2007 fails to fill it in in
429: * some cases. Use the same fallback strategy used by
430: * MimeMessage.
431: */
432: if (envelope.replyTo == null || envelope.replyTo.length == 0)
433: return getFrom();
434: return aaclone(envelope.replyTo);
435: }
436:
437: @Override
438: public void setReplyTo(Address[] addresses) throws MessagingException {
439: throw new IllegalWriteException("IMAPMessage is read-only");
440: }
441:
442: /**
443: * Get the decoded subject.
444: */
445: @Override
446: public String getSubject() throws MessagingException {
447: checkExpunged();
448: if (bodyLoaded)
449: return super.getSubject();
450:
451: if (subject != null) // already cached ?
452: return subject;
453:
454: loadEnvelope();
455: if (envelope.subject == null) // no subject
456: return null;
457:
458: // Cache and return the decoded value.
459: try {
460: // The server *should* unfold the value, but just in case it
461: // doesn't we unfold it here.
462: subject =
463: MimeUtility.decodeText(MimeUtility.unfold(envelope.subject));
464: } catch (UnsupportedEncodingException ex) {
465: subject = envelope.subject;
466: }
467:
468: return subject;
469: }
470:
471: @Override
472: public void setSubject(String subject, String charset)
473: throws MessagingException {
474: throw new IllegalWriteException("IMAPMessage is read-only");
475: }
476:
477: /**
478: * Get the SentDate.
479: */
480: @Override
481: public Date getSentDate() throws MessagingException {
482: checkExpunged();
483: if (bodyLoaded)
484: return super.getSentDate();
485: loadEnvelope();
486: if (envelope.date == null)
487: return null;
488: else
489: return new Date(envelope.date.getTime());
490: }
491:
492: @Override
493: public void setSentDate(Date d) throws MessagingException {
494: throw new IllegalWriteException("IMAPMessage is read-only");
495: }
496:
497: /**
498: * Get the received date (INTERNALDATE).
499: */
500: @Override
501: public Date getReceivedDate() throws MessagingException {
502: checkExpunged();
503: if (receivedDate == null)
504: loadEnvelope(); // have to go to the server for this
505: if (receivedDate == null)
506: return null;
507: else
508: return new Date(receivedDate.getTime());
509: }
510:
511: /**
512: * Get the message size. <p>
513: *
514: * Note that this returns RFC822.SIZE. That is, it's the
515: * size of the whole message, header and body included.
516: * Note also that if the size of the message is greater than
517: * Integer.MAX_VALUE (2GB), this method returns Integer.MAX_VALUE.
518: */
519: @Override
520: public int getSize() throws MessagingException {
521: checkExpunged();
522: // if bodyLoaded, size is already set
523: if (size == -1)
524: loadEnvelope(); // XXX - could just fetch the size
525: if (size > Integer.MAX_VALUE)
526: return Integer.MAX_VALUE; // the best we can do...
527: else
528: return (int) size;
529: }
530:
531: /**
532: * Get the message size as a long. <p>
533: *
534: * Suitable for messages that might be larger than 2GB.
535: *
536: * @return the message size as a long integer
537: * @exception MessagingException for failures
538: * @since JavaMail 1.6
539: */
540: public long getSizeLong() throws MessagingException {
541: checkExpunged();
542: // if bodyLoaded, size is already set
543: if (size == -1)
544: loadEnvelope(); // XXX - could just fetch the size
545: return size;
546: }
547:
548: /**
549: * Get the total number of lines. <p>
550: *
551: * Returns the "body_fld_lines" field from the
552: * BODYSTRUCTURE. Note that this field is available
553: * only for text/plain and message/rfc822 types
554: */
555: @Override
556: public int getLineCount() throws MessagingException {
557: checkExpunged();
558: // XXX - superclass doesn't implement this
559: loadBODYSTRUCTURE();
560: return bs.lines;
561: }
562:
563: /**
564: * Get the content language.
565: */
566: @Override
567: public String[] getContentLanguage() throws MessagingException {
568: checkExpunged();
569: if (bodyLoaded)
570: return super.getContentLanguage();
571: loadBODYSTRUCTURE();
572: if (bs.language != null)
573: return bs.language.clone();
574: else
575: return null;
576: }
577:
578: @Override
579: public void setContentLanguage(String[] languages)
580: throws MessagingException {
581: throw new IllegalWriteException("IMAPMessage is read-only");
582: }
583:
584: /**
585: * Get the In-Reply-To header.
586: *
587: * @return the In-Reply-To header
588: * @exception MessagingException for failures
589: * @since JavaMail 1.3.3
590: */
591: public String getInReplyTo() throws MessagingException {
592: checkExpunged();
593: if (bodyLoaded)
594: return super.getHeader("In-Reply-To", " ");
595: loadEnvelope();
596: return envelope.inReplyTo;
597: }
598:
599: /**
600: * Get the Content-Type.
601: *
602: * Generate this header from the BODYSTRUCTURE. Append parameters
603: * as well.
604: */
605: @Override
606: public synchronized String getContentType() throws MessagingException {
607: checkExpunged();
608: if (bodyLoaded)
609: return super.getContentType();
610:
611: // If we haven't cached the type yet ..
612: if (type == null) {
613: loadBODYSTRUCTURE();
614: // generate content-type from BODYSTRUCTURE
615: ContentType ct = new ContentType(bs.type, bs.subtype, bs.cParams);
616: type = ct.toString();
617: }
618: return type;
619: }
620:
621: /**
622: * Get the Content-Disposition.
623: */
624: @Override
625: public String getDisposition() throws MessagingException {
626: checkExpunged();
627: if (bodyLoaded)
628: return super.getDisposition();
629: loadBODYSTRUCTURE();
630: return bs.disposition;
631: }
632:
633: @Override
634: public void setDisposition(String disposition) throws MessagingException {
635: throw new IllegalWriteException("IMAPMessage is read-only");
636: }
637:
638: /**
639: * Get the Content-Transfer-Encoding.
640: */
641: @Override
642: public String getEncoding() throws MessagingException {
643: checkExpunged();
644: if (bodyLoaded)
645: return super.getEncoding();
646: loadBODYSTRUCTURE();
647: return bs.encoding;
648: }
649:
650: /**
651: * Get the Content-ID.
652: */
653: @Override
654: public String getContentID() throws MessagingException {
655: checkExpunged();
656: if (bodyLoaded)
657: return super.getContentID();
658: loadBODYSTRUCTURE();
659: return bs.id;
660: }
661:
662: @Override
663: public void setContentID(String cid) throws MessagingException {
664: throw new IllegalWriteException("IMAPMessage is read-only");
665: }
666:
667: /**
668: * Get the Content-MD5.
669: */
670: @Override
671: public String getContentMD5() throws MessagingException {
672: checkExpunged();
673: if (bodyLoaded)
674: return super.getContentMD5();
675: loadBODYSTRUCTURE();
676: return bs.md5;
677: }
678:
679: @Override
680: public void setContentMD5(String md5) throws MessagingException {
681: throw new IllegalWriteException("IMAPMessage is read-only");
682: }
683:
684: /**
685: * Get the decoded Content-Description.
686: */
687: @Override
688: public String getDescription() throws MessagingException {
689: checkExpunged();
690: if (bodyLoaded)
691: return super.getDescription();
692:
693: if (description != null) // cached value ?
694: return description;
695:
696: loadBODYSTRUCTURE();
697: if (bs.description == null)
698: return null;
699:
700: try {
701: description = MimeUtility.decodeText(bs.description);
702: } catch (UnsupportedEncodingException ex) {
703: description = bs.description;
704: }
705:
706: return description;
707: }
708:
709: @Override
710: public void setDescription(String description, String charset)
711: throws MessagingException {
712: throw new IllegalWriteException("IMAPMessage is read-only");
713: }
714:
715: /**
716: * Get the Message-ID.
717: */
718: @Override
719: public String getMessageID() throws MessagingException {
720: checkExpunged();
721: if (bodyLoaded)
722: return super.getMessageID();
723: loadEnvelope();
724: return envelope.messageId;
725: }
726:
727: /**
728: * Get the "filename" Disposition parameter. (Only available in
729: * IMAP4rev1). If thats not available, get the "name" ContentType
730: * parameter.
731: */
732: @Override
733: public String getFileName() throws MessagingException {
734: checkExpunged();
735: if (bodyLoaded)
736: return super.getFileName();
737:
738: String filename = null;
739: loadBODYSTRUCTURE();
740:
741: if (bs.dParams != null)
742: filename = bs.dParams.get("filename");
743: if (filename == null && bs.cParams != null)
744: filename = bs.cParams.get("name");
745: return filename;
746: }
747:
748: @Override
749: public void setFileName(String filename) throws MessagingException {
750: throw new IllegalWriteException("IMAPMessage is read-only");
751: }
752:
753: /**
754: * Get all the bytes for this message. Overrides getContentStream()
755: * in MimeMessage. This method is ultimately used by the DataHandler
756: * to obtain the input stream for this message.
757: *
758: * @see jakarta.mail.internet.MimeMessage#getContentStream
759: */
760: @Override
761: protected InputStream getContentStream() throws MessagingException {
762: if (bodyLoaded)
763: return super.getContentStream();
764: InputStream is = null;
765: boolean pk = getPeek(); // get before acquiring message cache lock
766:
767: // Acquire MessageCacheLock, to freeze seqnum.
768: synchronized (getMessageCacheLock()) {
769: try {
770: IMAPProtocol p = getProtocol();
771:
772: // This message could be expunged when we were waiting
773: // to acquire the lock ...
774: checkExpunged();
775:
776: if (p.isREV1() && (getFetchBlockSize() != -1)) // IMAP4rev1
777: return new IMAPInputStream(this, toSection("TEXT"),
778: bs != null && !ignoreBodyStructureSize() ?
779: bs.size : -1, pk);
780:
781: if (p.isREV1()) {
782: BODY b;
783: if (pk)
784: b = p.peekBody(getSequenceNumber(), toSection("TEXT"));
785: else
786: b = p.fetchBody(getSequenceNumber(), toSection("TEXT"));
787: if (b != null)
788: is = b.getByteArrayInputStream();
789: } else {
790: RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), "TEXT");
791: if (rd != null)
792: is = rd.getByteArrayInputStream();
793: }
794: } catch (ConnectionException cex) {
795: throw new FolderClosedException(folder, cex.getMessage());
796: } catch (ProtocolException pex) {
797: forceCheckExpunged();
798: throw new MessagingException(pex.getMessage(), pex);
799: }
800: }
801:
802: if (is == null) {
803: forceCheckExpunged(); // may throw MessageRemovedException
804: // nope, the server doesn't think it's expunged.
805: // can't tell the difference between the server returning NIL
806: // and some other error that caused null to be returned above,
807: // so we'll just assume it was empty content.
808: is = new ByteArrayInputStream(new byte[0]);
809: }
810: return is;
811: }
812:
813: /**
814: * Get the DataHandler object for this message.
815: */
816: @Override
817: public synchronized DataHandler getDataHandler()
818: throws MessagingException {
819: checkExpunged();
820:
821: if (dh == null && !bodyLoaded) {
822: loadBODYSTRUCTURE();
823: if (type == null) { // type not yet computed
824: // generate content-type from BODYSTRUCTURE
825: ContentType ct = new ContentType(bs.type, bs.subtype,
826: bs.cParams);
827: type = ct.toString();
828: }
829:
830: /* Special-case Multipart and Nested content. All other
831: * cases are handled by the superclass.
832: */
833: if (bs.isMulti())
834: dh = new DataHandler(
835: new IMAPMultipartDataSource(this, bs.bodies,
836: sectionId, this)
837: );
838: else if (bs.isNested() && isREV1() && bs.envelope != null)
839: /* Nested messages are handled specially only for
840: * IMAP4rev1. IMAP4 doesn't provide enough support to
841: * FETCH the components of nested messages
842: */
843: dh = new DataHandler(
844: new IMAPNestedMessage(this,
845: bs.bodies[0],
846: bs.envelope,
847: sectionId == null ? "1" : sectionId + ".1"),
848: type
849: );
850: }
851:
852: return super.getDataHandler();
853: }
854:
855: @Override
856: public void setDataHandler(DataHandler content)
857: throws MessagingException {
858: throw new IllegalWriteException("IMAPMessage is read-only");
859: }
860:
861: /**
862: * Return the MIME format stream corresponding to this message.
863: *
864: * @return the MIME format stream
865: * @since JavaMail 1.4.5
866: */
867: @Override
868: public InputStream getMimeStream() throws MessagingException {
869: // XXX - need an "if (bodyLoaded)" version
870: InputStream is = null;
871: boolean pk = getPeek(); // get before acquiring message cache lock
872:
873: // Acquire MessageCacheLock, to freeze seqnum.
874: synchronized (getMessageCacheLock()) {
875: try {
876: IMAPProtocol p = getProtocol();
877:
878: checkExpunged(); // insure this message is not expunged
879:
880: if (p.isREV1() && (getFetchBlockSize() != -1)) // IMAP4rev1
881: return new IMAPInputStream(this, sectionId, -1, pk);
882:
883: if (p.isREV1()) {
884: BODY b;
885: if (pk)
886: b = p.peekBody(getSequenceNumber(), sectionId);
887: else
888: b = p.fetchBody(getSequenceNumber(), sectionId);
889: if (b != null)
890: is = b.getByteArrayInputStream();
891: } else {
892: RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), null);
893: if (rd != null)
894: is = rd.getByteArrayInputStream();
895: }
896: } catch (ConnectionException cex) {
897: throw new FolderClosedException(folder, cex.getMessage());
898: } catch (ProtocolException pex) {
899: forceCheckExpunged();
900: throw new MessagingException(pex.getMessage(), pex);
901: }
902: }
903:
904: if (is == null) {
905: forceCheckExpunged(); // may throw MessageRemovedException
906: // nope, the server doesn't think it's expunged.
907: // can't tell the difference between the server returning NIL
908: // and some other error that caused null to be returned above,
909: // so we'll just assume it was empty content.
910: is = new ByteArrayInputStream(new byte[0]);
911: }
912: return is;
913: }
914:
915: /**
916: * Write out the bytes into the given OutputStream.
917: */
918: @Override
919: public void writeTo(OutputStream os)
920: throws IOException, MessagingException {
921: if (bodyLoaded) {
922: super.writeTo(os);
923: return;
924: }
925: InputStream is = getMimeStream();
926: try {
927: // write out the bytes
928: byte[] bytes = new byte[16 * 1024];
929: int count;
930: while ((count = is.read(bytes)) != -1)
931: os.write(bytes, 0, count);
932: } finally {
933: is.close();
934: }
935: }
936:
937: /**
938: * Get the named header.
939: */
940: @Override
941: public String[] getHeader(String name) throws MessagingException {
942: checkExpunged();
943:
944: if (isHeaderLoaded(name)) // already loaded ?
945: return headers.getHeader(name);
946:
947: // Load this particular header
948: InputStream is = null;
949:
950: // Acquire MessageCacheLock, to freeze seqnum.
951: synchronized (getMessageCacheLock()) {
952: try {
953: IMAPProtocol p = getProtocol();
954:
955: // This message could be expunged when we were waiting
956: // to acquire the lock ...
957: checkExpunged();
958:
959: if (p.isREV1()) {
960: BODY b = p.peekBody(getSequenceNumber(),
961: toSection("HEADER.FIELDS (" + name + ")")
962: );
963: if (b != null)
964: is = b.getByteArrayInputStream();
965: } else {
966: RFC822DATA rd = p.fetchRFC822(getSequenceNumber(),
967: "HEADER.LINES (" + name + ")");
968: if (rd != null)
969: is = rd.getByteArrayInputStream();
970: }
971: } catch (ConnectionException cex) {
972: throw new FolderClosedException(folder, cex.getMessage());
973: } catch (ProtocolException pex) {
974: forceCheckExpunged();
975: throw new MessagingException(pex.getMessage(), pex);
976: }
977: }
978:
979: // if we get this far without "is" being set, something has gone
980: // wrong; prevent a later NullPointerException and return null here
981: if (is == null)
982: return null;
983:
984: if (headers == null)
985: headers = new InternetHeaders();
986: headers.load(is); // load this header into the Headers object.
987: setHeaderLoaded(name); // Mark this header as loaded
988:
989: return headers.getHeader(name);
990: }
991:
992: /**
993: * Get the named header.
994: */
995: @Override
996: public String getHeader(String name, String delimiter)
997: throws MessagingException {
998: checkExpunged();
999:
1000: // force the header to be loaded by invoking getHeader(name)
1001: if (getHeader(name) == null)
1002: return null;
1003: return headers.getHeader(name, delimiter);
1004: }
1005:
1006: @Override
1007: public void setHeader(String name, String value)
1008: throws MessagingException {
1009: throw new IllegalWriteException("IMAPMessage is read-only");
1010: }
1011:
1012: @Override
1013: public void addHeader(String name, String value)
1014: throws MessagingException {
1015: throw new IllegalWriteException("IMAPMessage is read-only");
1016: }
1017:
1018: @Override
1019: public void removeHeader(String name)
1020: throws MessagingException {
1021: throw new IllegalWriteException("IMAPMessage is read-only");
1022: }
1023:
1024: /**
1025: * Get all headers.
1026: */
1027: @Override
1028: public Enumeration<Header> getAllHeaders() throws MessagingException {
1029: checkExpunged();
1030: loadHeaders();
1031: return super.getAllHeaders();
1032: }
1033:
1034: /**
1035: * Get matching headers.
1036: */
1037: @Override
1038: public Enumeration<Header> getMatchingHeaders(String[] names)
1039: throws MessagingException {
1040: checkExpunged();
1041: loadHeaders();
1042: return super.getMatchingHeaders(names);
1043: }
1044:
1045: /**
1046: * Get non-matching headers.
1047: */
1048: @Override
1049: public Enumeration<Header> getNonMatchingHeaders(String[] names)
1050: throws MessagingException {
1051: checkExpunged();
1052: loadHeaders();
1053: return super.getNonMatchingHeaders(names);
1054: }
1055:
1056: @Override
1057: public void addHeaderLine(String line) throws MessagingException {
1058: throw new IllegalWriteException("IMAPMessage is read-only");
1059: }
1060:
1061: /**
1062: * Get all header-lines.
1063: */
1064: @Override
1065: public Enumeration<String> getAllHeaderLines() throws MessagingException {
1066: checkExpunged();
1067: loadHeaders();
1068: return super.getAllHeaderLines();
1069: }
1070:
1071: /**
1072: * Get all matching header-lines.
1073: */
1074: @Override
1075: public Enumeration<String> getMatchingHeaderLines(String[] names)
1076: throws MessagingException {
1077: checkExpunged();
1078: loadHeaders();
1079: return super.getMatchingHeaderLines(names);
1080: }
1081:
1082: /**
1083: * Get all non-matching headerlines.
1084: */
1085: @Override
1086: public Enumeration<String> getNonMatchingHeaderLines(String[] names)
1087: throws MessagingException {
1088: checkExpunged();
1089: loadHeaders();
1090: return super.getNonMatchingHeaderLines(names);
1091: }
1092:
1093: /**
1094: * Get the Flags for this message.
1095: */
1096: @Override
1097: public synchronized Flags getFlags() throws MessagingException {
1098: checkExpunged();
1099: loadFlags();
1100: return super.getFlags();
1101: }
1102:
1103: /**
1104: * Test if the given Flags are set in this message.
1105: */
1106: @Override
1107: public synchronized boolean isSet(Flags.Flag flag)
1108: throws MessagingException {
1109: checkExpunged();
1110: loadFlags();
1111: return super.isSet(flag);
1112: }
1113:
1114: /**
1115: * Set/Unset the given flags in this message.
1116: */
1117: @Override
1118: public synchronized void setFlags(Flags flag, boolean set)
1119: throws MessagingException {
1120: // Acquire MessageCacheLock, to freeze seqnum.
1121: synchronized (getMessageCacheLock()) {
1122: try {
1123: IMAPProtocol p = getProtocol();
1124: checkExpunged(); // Insure that this message is not expunged
1125: p.storeFlags(getSequenceNumber(), flag, set);
1126: } catch (ConnectionException cex) {
1127: throw new FolderClosedException(folder, cex.getMessage());
1128: } catch (ProtocolException pex) {
1129: throw new MessagingException(pex.getMessage(), pex);
1130: }
1131: }
1132: }
1133:
1134: /**
1135: * Set whether or not to use the PEEK variant of FETCH when
1136: * fetching message content. This overrides the default
1137: * value from the "mail.imap.peek" property.
1138: *
1139: * @param peek the peek flag
1140: * @since JavaMail 1.3.3
1141: */
1142: public synchronized void setPeek(boolean peek) {
1143: this.peek = Boolean.valueOf(peek);
1144: }
1145:
1146: /**
1147: * Get whether or not to use the PEEK variant of FETCH when
1148: * fetching message content.
1149: *
1150: * @return the peek flag
1151: * @since JavaMail 1.3.3
1152: */
1153: public synchronized boolean getPeek() {
1154: if (peek == null)
1155: return ((IMAPStore) folder.getStore()).getPeek();
1156: else
1157: return peek.booleanValue();
1158: }
1159:
1160: /**
1161: * Invalidate cached header and envelope information for this
1162: * message. Subsequent accesses of this information will
1163: * cause it to be fetched from the server.
1164: *
1165: * @since JavaMail 1.3.3
1166: */
1167: public synchronized void invalidateHeaders() {
1168: headersLoaded = false;
1169: loadedHeaders.clear();
1170: headers = null;
1171: envelope = null;
1172: bs = null;
1173: receivedDate = null;
1174: size = -1;
1175: type = null;
1176: subject = null;
1177: description = null;
1178: flags = null;
1179: content = null;
1180: contentStream = null;
1181: bodyLoaded = false;
1182: }
1183:
1184: /**
1185: * This class implements the test to be done on each
1186: * message in the folder. The test is to check whether the
1187: * message has already cached all the items requested in the
1188: * FetchProfile. If any item is missing, the test succeeds and
1189: * breaks out.
1190: */
1191: public static class FetchProfileCondition implements Utility.Condition {
1192: private boolean needEnvelope = false;
1193: private boolean needFlags = false;
1194: private boolean needBodyStructure = false;
1195: private boolean needUID = false;
1196: private boolean needHeaders = false;
1197: private boolean needSize = false;
1198: private boolean needMessage = false;
1199: private boolean needRDate = false;
1200: private String[] hdrs = null;
1201: private Set<FetchItem> need = new HashSet<>();
1202:
1203: /**
1204: * Create a FetchProfileCondition to determine if we need to fetch
1205: * any of the information specified in the FetchProfile.
1206: *
1207: * @param fp the FetchProfile
1208: * @param fitems the FETCH items
1209: */
1210: @SuppressWarnings("deprecation") // for FetchProfile.Item.SIZE
1211: public FetchProfileCondition(FetchProfile fp, FetchItem[] fitems) {
1212:• if (fp.contains(FetchProfile.Item.ENVELOPE))
1213: needEnvelope = true;
1214:• if (fp.contains(FetchProfile.Item.FLAGS))
1215: needFlags = true;
1216:• if (fp.contains(FetchProfile.Item.CONTENT_INFO))
1217: needBodyStructure = true;
1218:• if (fp.contains(FetchProfile.Item.SIZE))
1219: needSize = true;
1220:• if (fp.contains(UIDFolder.FetchProfileItem.UID))
1221: needUID = true;
1222:• if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS))
1223: needHeaders = true;
1224:• if (fp.contains(IMAPFolder.FetchProfileItem.SIZE))
1225: needSize = true;
1226:• if (fp.contains(IMAPFolder.FetchProfileItem.MESSAGE))
1227: needMessage = true;
1228:• if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE))
1229: needRDate = true;
1230: hdrs = fp.getHeaderNames();
1231:• for (int i = 0; i < fitems.length; i++) {
1232:• if (fp.contains(fitems[i].getFetchProfileItem()))
1233: need.add(fitems[i]);
1234: }
1235: }
1236:
1237: /**
1238: * Return true if we NEED to fetch the requested information
1239: * for the specified message.
1240: */
1241: @Override
1242: public boolean test(IMAPMessage m) {
1243:• if (needEnvelope && m._getEnvelope() == null && !m.bodyLoaded)
1244: return true; // no envelope
1245:• if (needFlags && m._getFlags() == null)
1246: return true; // no flags
1247:• if (needBodyStructure && m._getBodyStructure() == null &&
1248:• !m.bodyLoaded)
1249: return true; // no BODYSTRUCTURE
1250:• if (needUID && m.getUID() == -1) // no UID
1251: return true;
1252:• if (needHeaders && !m.areHeadersLoaded()) // no headers
1253: return true;
1254:• if (needSize && m.size == -1 && !m.bodyLoaded) // no size
1255: return true;
1256:• if (needMessage && !m.bodyLoaded) // no message body
1257: return true;
1258:• if (needRDate && m.receivedDate == null) // no received date
1259: return true;
1260:
1261: // Is the desired header present ?
1262:• for (int i = 0; i < hdrs.length; i++) {
1263:• if (!m.isHeaderLoaded(hdrs[i]))
1264: return true; // Nope, return
1265: }
1266: Iterator<FetchItem> it = need.iterator();
1267:• while (it.hasNext()) {
1268: FetchItem fitem = it.next();
1269:• if (m.items == null || m.items.get(fitem.getName()) == null)
1270: return true;
1271: }
1272:
1273: return false;
1274: }
1275: }
1276:
1277: /**
1278: * Apply the data in the FETCH item to this message.
1279: *
1280: * ASSERT: Must hold the messageCacheLock.
1281: *
1282: * @param item the fetch item
1283: * @param hdrs the headers we're asking for
1284: * @param allHeaders load all headers?
1285: * @return did we handle this fetch item?
1286: * @exception MessagingException for failures
1287: * @since JavaMail 1.4.6
1288: */
1289: protected boolean handleFetchItem(Item item,
1290: String[] hdrs, boolean allHeaders)
1291: throws MessagingException {
1292: // Check for the FLAGS item
1293: if (item instanceof Flags)
1294: flags = (Flags) item;
1295: // Check for ENVELOPE items
1296: else if (item instanceof ENVELOPE)
1297: envelope = (ENVELOPE) item;
1298: else if (item instanceof INTERNALDATE)
1299: receivedDate = ((INTERNALDATE) item).getDate();
1300: else if (item instanceof RFC822SIZE)
1301: size = ((RFC822SIZE) item).size;
1302: else if (item instanceof MODSEQ)
1303: modseq = ((MODSEQ) item).modseq;
1304:
1305: // Check for the BODYSTRUCTURE item
1306: else if (item instanceof BODYSTRUCTURE)
1307: bs = (BODYSTRUCTURE) item;
1308: // Check for the UID item
1309: else if (item instanceof UID) {
1310: UID u = (UID) item;
1311: uid = u.uid; // set uid
1312: // add entry into uid table
1313: if (((IMAPFolder) folder).uidTable == null)
1314: ((IMAPFolder) folder).uidTable
1315: = new Hashtable<>();
1316: ((IMAPFolder) folder).uidTable.put(Long.valueOf(u.uid), this);
1317: }
1318:
1319: // Check for header items
1320: else if (item instanceof RFC822DATA ||
1321: item instanceof BODY) {
1322: InputStream headerStream;
1323: boolean isHeader;
1324: if (item instanceof RFC822DATA) { // IMAP4
1325: headerStream =
1326: ((RFC822DATA) item).getByteArrayInputStream();
1327: isHeader = ((RFC822DATA) item).isHeader();
1328: } else { // IMAP4rev1
1329: headerStream =
1330: ((BODY) item).getByteArrayInputStream();
1331: isHeader = ((BODY) item).isHeader();
1332: }
1333:
1334: if (!isHeader) {
1335: // load the entire message by using the superclass
1336: // MimeMessage.parse method
1337: // first, save the size of the message
1338: try {
1339: size = headerStream.available();
1340: } catch (IOException ex) {
1341: // should never occur
1342: }
1343: parse(headerStream);
1344: bodyLoaded = true;
1345: setHeadersLoaded(true);
1346: } else {
1347: // Load the obtained headers.
1348: InternetHeaders h = new InternetHeaders();
1349: // Some IMAP servers (e.g., gmx.net) return NIL
1350: // instead of a string just containing a CR/LF
1351: // when the header list is empty.
1352: if (headerStream != null)
1353: h.load(headerStream);
1354: if (headers == null || allHeaders)
1355: headers = h;
1356: else {
1357: /*
1358: * This is really painful. A second fetch
1359: * of the same headers (which might occur because
1360: * a new header was added to the set requested)
1361: * will return headers we already know about.
1362: * In this case, only load the headers we haven't
1363: * seen before to avoid adding duplicates of
1364: * headers we already have.
1365: *
1366: * XXX - There's a race condition here if another
1367: * thread is reading headers in the same message
1368: * object, because InternetHeaders is not thread
1369: * safe.
1370: */
1371: Enumeration<Header> e = h.getAllHeaders();
1372: while (e.hasMoreElements()) {
1373: Header he = e.nextElement();
1374: if (!isHeaderLoaded(he.getName()))
1375: headers.addHeader(
1376: he.getName(), he.getValue());
1377: }
1378: }
1379:
1380: // if we asked for all headers, assume we got them
1381: if (allHeaders)
1382: setHeadersLoaded(true);
1383: else {
1384: // Mark all headers we asked for as 'loaded'
1385: for (int k = 0; k < hdrs.length; k++)
1386: setHeaderLoaded(hdrs[k]);
1387: }
1388: }
1389: } else
1390: return false; // not handled
1391: return true; // something above handled it
1392: }
1393:
1394: /**
1395: * Apply the data in the extension FETCH items to this message.
1396: * This method adds all the items to the items map.
1397: * Subclasses may override this method to call super and then
1398: * also copy the data to a more convenient form.
1399: *
1400: * ASSERT: Must hold the messageCacheLock.
1401: *
1402: * @param extensionItems the Map to add fetch items to
1403: * @since JavaMail 1.4.6
1404: */
1405: protected void handleExtensionFetchItems(
1406: Map<String, Object> extensionItems) {
1407: if (extensionItems == null || extensionItems.isEmpty())
1408: return;
1409: if (items == null)
1410: items = new HashMap<>();
1411: items.putAll(extensionItems);
1412: }
1413:
1414: /**
1415: * Fetch an individual item for the current message.
1416: * Note that handleExtensionFetchItems will have been called
1417: * to store this item in the message before this method
1418: * returns.
1419: *
1420: * @param fitem the FetchItem
1421: * @return the data associated with the FetchItem
1422: * @exception MessagingException for failures
1423: * @since JavaMail 1.4.6
1424: */
1425: protected Object fetchItem(FetchItem fitem)
1426: throws MessagingException {
1427:
1428: // Acquire MessageCacheLock, to freeze seqnum.
1429: synchronized (getMessageCacheLock()) {
1430: Object robj = null;
1431:
1432: try {
1433: IMAPProtocol p = getProtocol();
1434:
1435: checkExpunged(); // Insure that this message is not expunged
1436:
1437: int seqnum = getSequenceNumber();
1438: Response[] r = p.fetch(seqnum, fitem.getName());
1439:
1440: for (int i = 0; i < r.length; i++) {
1441: // If this response is NOT a FetchResponse or if it does
1442: // not match our seqnum, skip.
1443: if (r[i] == null ||
1444: !(r[i] instanceof FetchResponse) ||
1445: ((FetchResponse) r[i]).getNumber() != seqnum)
1446: continue;
1447:
1448: FetchResponse f = (FetchResponse) r[i];
1449: handleExtensionFetchItems(f.getExtensionItems());
1450: if (items != null) {
1451: Object o = items.get(fitem.getName());
1452: if (o != null)
1453: robj = o;
1454: }
1455: }
1456:
1457: // ((IMAPFolder)folder).handleResponses(r);
1458: p.notifyResponseHandlers(r);
1459: p.handleResult(r[r.length - 1]);
1460: } catch (ConnectionException cex) {
1461: throw new FolderClosedException(folder, cex.getMessage());
1462: } catch (ProtocolException pex) {
1463: forceCheckExpunged();
1464: throw new MessagingException(pex.getMessage(), pex);
1465: }
1466: return robj;
1467:
1468: } // Release MessageCacheLock
1469: }
1470:
1471: /**
1472: * Return the data associated with the FetchItem.
1473: * If the data hasn't been fetched, call the fetchItem
1474: * method to fetch it. Returns null if there is no
1475: * data for the FetchItem.
1476: *
1477: * @param fitem the FetchItem
1478: * @return the data associated with the FetchItem
1479: * @exception MessagingException for failures
1480: * @since JavaMail 1.4.6
1481: */
1482: public synchronized Object getItem(FetchItem fitem)
1483: throws MessagingException {
1484: Object item = items == null ? null : items.get(fitem.getName());
1485: if (item == null)
1486: item = fetchItem(fitem);
1487: return item;
1488: }
1489:
1490: /*
1491: * Load the Envelope for this message.
1492: */
1493: private synchronized void loadEnvelope() throws MessagingException {
1494: if (envelope != null) // already loaded
1495: return;
1496:
1497: Response[] r = null;
1498:
1499: // Acquire MessageCacheLock, to freeze seqnum.
1500: synchronized (getMessageCacheLock()) {
1501: try {
1502: IMAPProtocol p = getProtocol();
1503:
1504: checkExpunged(); // Insure that this message is not expunged
1505:
1506: int seqnum = getSequenceNumber();
1507: r = p.fetch(seqnum, EnvelopeCmd);
1508:
1509: for (int i = 0; i < r.length; i++) {
1510: // If this response is NOT a FetchResponse or if it does
1511: // not match our seqnum, skip.
1512: if (r[i] == null ||
1513: !(r[i] instanceof FetchResponse) ||
1514: ((FetchResponse) r[i]).getNumber() != seqnum)
1515: continue;
1516:
1517: FetchResponse f = (FetchResponse) r[i];
1518:
1519: // Look for the Envelope items.
1520: int count = f.getItemCount();
1521: for (int j = 0; j < count; j++) {
1522: Item item = f.getItem(j);
1523:
1524: if (item instanceof ENVELOPE)
1525: envelope = (ENVELOPE) item;
1526: else if (item instanceof INTERNALDATE)
1527: receivedDate = ((INTERNALDATE) item).getDate();
1528: else if (item instanceof RFC822SIZE)
1529: size = ((RFC822SIZE) item).size;
1530: }
1531: }
1532:
1533: // ((IMAPFolder)folder).handleResponses(r);
1534: p.notifyResponseHandlers(r);
1535: p.handleResult(r[r.length - 1]);
1536: } catch (ConnectionException cex) {
1537: throw new FolderClosedException(folder, cex.getMessage());
1538: } catch (ProtocolException pex) {
1539: forceCheckExpunged();
1540: throw new MessagingException(pex.getMessage(), pex);
1541: }
1542:
1543: } // Release MessageCacheLock
1544:
1545: if (envelope == null)
1546: throw new MessagingException("Failed to load IMAP envelope");
1547: }
1548:
1549: /*
1550: * Load the BODYSTRUCTURE
1551: */
1552: private synchronized void loadBODYSTRUCTURE()
1553: throws MessagingException {
1554: if (bs != null) // already loaded
1555: return;
1556:
1557: // Acquire MessageCacheLock, to freeze seqnum.
1558: synchronized (getMessageCacheLock()) {
1559: try {
1560: IMAPProtocol p = getProtocol();
1561:
1562: // This message could be expunged when we were waiting
1563: // to acquire the lock ...
1564: checkExpunged();
1565:
1566: bs = p.fetchBodyStructure(getSequenceNumber());
1567: } catch (ConnectionException cex) {
1568: throw new FolderClosedException(folder, cex.getMessage());
1569: } catch (ProtocolException pex) {
1570: forceCheckExpunged();
1571: throw new MessagingException(pex.getMessage(), pex);
1572: }
1573: if (bs == null) {
1574: // if the FETCH is successful, we should always get a
1575: // BODYSTRUCTURE, but some servers fail to return it
1576: // if the message has been expunged
1577: forceCheckExpunged();
1578: throw new MessagingException("Unable to load BODYSTRUCTURE");
1579: }
1580: }
1581: }
1582:
1583: /*
1584: * Load all headers.
1585: */
1586: private synchronized void loadHeaders() throws MessagingException {
1587: if (headersLoaded)
1588: return;
1589:
1590: InputStream is = null;
1591:
1592: // Acquire MessageCacheLock, to freeze seqnum.
1593: synchronized (getMessageCacheLock()) {
1594: try {
1595: IMAPProtocol p = getProtocol();
1596:
1597: // This message could be expunged when we were waiting
1598: // to acquire the lock ...
1599: checkExpunged();
1600:
1601: if (p.isREV1()) {
1602: BODY b = p.peekBody(getSequenceNumber(),
1603: toSection("HEADER"));
1604: if (b != null)
1605: is = b.getByteArrayInputStream();
1606: } else {
1607: RFC822DATA rd = p.fetchRFC822(getSequenceNumber(),
1608: "HEADER");
1609: if (rd != null)
1610: is = rd.getByteArrayInputStream();
1611: }
1612: } catch (ConnectionException cex) {
1613: throw new FolderClosedException(folder, cex.getMessage());
1614: } catch (ProtocolException pex) {
1615: forceCheckExpunged();
1616: throw new MessagingException(pex.getMessage(), pex);
1617: }
1618: } // Release MessageCacheLock
1619:
1620: if (is == null)
1621: throw new MessagingException("Cannot load header");
1622: headers = new InternetHeaders(is);
1623: headersLoaded = true;
1624: }
1625:
1626: /*
1627: * Load this message's Flags
1628: */
1629: private synchronized void loadFlags() throws MessagingException {
1630: if (flags != null)
1631: return;
1632:
1633: // Acquire MessageCacheLock, to freeze seqnum.
1634: synchronized (getMessageCacheLock()) {
1635: try {
1636: IMAPProtocol p = getProtocol();
1637:
1638: // This message could be expunged when we were waiting
1639: // to acquire the lock ...
1640: checkExpunged();
1641:
1642: flags = p.fetchFlags(getSequenceNumber());
1643: // make sure flags is always set, even if server is broken
1644: if (flags == null)
1645: flags = new Flags();
1646: } catch (ConnectionException cex) {
1647: throw new FolderClosedException(folder, cex.getMessage());
1648: } catch (ProtocolException pex) {
1649: forceCheckExpunged();
1650: throw new MessagingException(pex.getMessage(), pex);
1651: }
1652: } // Release MessageCacheLock
1653: }
1654:
1655: /*
1656: * Are all headers loaded?
1657: */
1658: private boolean areHeadersLoaded() {
1659: return headersLoaded;
1660: }
1661:
1662: /*
1663: * Set whether all headers are loaded.
1664: */
1665: private void setHeadersLoaded(boolean loaded) {
1666: headersLoaded = loaded;
1667: }
1668:
1669: /*
1670: * Check if the given header was ever loaded from the server
1671: */
1672: private boolean isHeaderLoaded(String name) {
1673: if (headersLoaded) // All headers for this message have been loaded
1674: return true;
1675:
1676: return loadedHeaders.containsKey(name.toUpperCase(Locale.ENGLISH));
1677: }
1678:
1679: /*
1680: * Mark that the given headers have been loaded from the server.
1681: */
1682: private void setHeaderLoaded(String name) {
1683: loadedHeaders.put(name.toUpperCase(Locale.ENGLISH), name);
1684: }
1685:
1686: /*
1687: * Convert the given FETCH item identifier to the approriate
1688: * section-string for this message.
1689: */
1690: private String toSection(String what) {
1691: if (sectionId == null)
1692: return what;
1693: else
1694: return sectionId + "." + what;
1695: }
1696:
1697: /*
1698: * Clone an array of InternetAddresses.
1699: */
1700: private InternetAddress[] aaclone(InternetAddress[] aa) {
1701: if (aa == null)
1702: return null;
1703: else
1704: return aa.clone();
1705: }
1706:
1707: private Flags _getFlags() {
1708: return flags;
1709: }
1710:
1711: private ENVELOPE _getEnvelope() {
1712: return envelope;
1713: }
1714:
1715: private BODYSTRUCTURE _getBodyStructure() {
1716: return bs;
1717: }
1718:
1719: /***********************************************************
1720: * accessor routines to make available certain private/protected
1721: * fields to other classes in this package.
1722: ***********************************************************/
1723:
1724: /*
1725: * Called by IMAPFolder.
1726: * Must not be synchronized.
1727: */
1728: void _setFlags(Flags flags) {
1729: this.flags = flags;
1730: }
1731:
1732: /*
1733: * Called by IMAPNestedMessage.
1734: */
1735: Session _getSession() {
1736: return session;
1737: }
1738: }