/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can obtain * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt. * Sun designates this particular file as subject to the "Classpath" exception * as provided by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the License * Header, with the fields enclosed by brackets [] replaced by your own * identifying information: "Portions Copyrighted [year] * [name of copyright owner]" * * Contributor(s): * * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.mail.imap; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Locale; import java.util.Vector; import javax.activation.DataHandler; import javax.mail.Address; import javax.mail.FetchProfile; import javax.mail.Flags; import javax.mail.FolderClosedException; import javax.mail.Header; import javax.mail.IllegalWriteException; import javax.mail.Message; import javax.mail.MessageRemovedException; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.UIDFolder; import javax.mail.internet.ContentType; import javax.mail.internet.InternetAddress; import javax.mail.internet.InternetHeaders; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeUtility; import com.sun.mail.iap.CommandFailedException; import com.sun.mail.iap.ConnectionException; import com.sun.mail.iap.ProtocolException; import com.sun.mail.iap.Response; import com.sun.mail.imap.protocol.BODY; import com.sun.mail.imap.protocol.BODYSTRUCTURE; import com.sun.mail.imap.protocol.ENVELOPE; import com.sun.mail.imap.protocol.FetchResponse; import com.sun.mail.imap.protocol.IMAPProtocol; import com.sun.mail.imap.protocol.INTERNALDATE; import com.sun.mail.imap.protocol.Item; import com.sun.mail.imap.protocol.MessageSet; import com.sun.mail.imap.protocol.RFC822DATA; import com.sun.mail.imap.protocol.RFC822SIZE; import com.sun.mail.imap.protocol.UID; /** * This class implements an IMAPMessage object. <p> * * An IMAPMessage object starts out as a light-weight object. It gets * filled-in incrementally when a request is made for some item. Or * when a prefetch is done using the FetchProfile. <p> * * An IMAPMessage has a messageNumber and a sequenceNumber. The * messageNumber is its index into its containing folder's messageCache. * The sequenceNumber is its IMAP sequence-number. * * @author John Mani * @author Bill Shannon */ /* * The lock hierarchy is that the lock on the IMAPMessage object, if * it's acquired at all, must be acquired before the message cache lock. * The IMAPMessage lock protects the message flags, sort of. * * XXX - I'm not convinced that all fields of IMAPMessage are properly * protected by locks. */ public class IMAPMessage extends MimeMessage { protected BODYSTRUCTURE bs; // BODYSTRUCTURE protected ENVELOPE envelope; // ENVELOPE private Date receivedDate; // INTERNALDATE private int size = -1; // RFC822.SIZE private boolean peek; // use BODY.PEEK when fetching content? // this message's IMAP UID private long uid = -1; // this message's IMAP sectionId (null for toplevel message, // non-null for a nested message) protected String sectionId; // processed values private String type; // Content-Type (with params) private String subject; // decoded (Unicode) subject private String description; // decoded (Unicode) desc // Indicates that we've loaded *all* headers for this message private boolean headersLoaded = false; /* Hashtable of names of headers we've loaded from the server. * Used in isHeaderLoaded() and setHeaderLoaded() to keep track * of those headers we've attempted to load from the server. We * need this table of names to avoid multiple attempts at loading * headers that don't exist for a particular message. * * Could this somehow be included in the InternetHeaders object ?? */ private Hashtable loadedHeaders; // This is our Envelope private static String EnvelopeCmd = "ENVELOPE INTERNALDATE RFC822.SIZE"; /** * Constructor. */ protected IMAPMessage(IMAPFolder folder, int msgnum) { super(folder, msgnum); flags = null; } /** * Constructor, for use by IMAPNestedMessage. */ protected IMAPMessage(Session session) { super(session); } /** * Get this message's folder's protocol connection. * Throws FolderClosedException, if the protocol connection * is not available. * * ASSERT: Must hold the messageCacheLock. */ protected IMAPProtocol getProtocol() throws ProtocolException, FolderClosedException { ((IMAPFolder)folder).waitIfIdle(); IMAPProtocol p = ((IMAPFolder)folder).protocol; if (p == null) throw new FolderClosedException(folder); else return p; } /* * Is this an IMAP4 REV1 server? */ protected boolean isREV1() throws FolderClosedException { // access the folder's protocol object without waiting // for IDLE to complete IMAPProtocol p = ((IMAPFolder)folder).protocol; if (p == null) throw new FolderClosedException(folder); else return p.isREV1(); } /** * Get the messageCacheLock, associated with this Message's * Folder. */ protected Object getMessageCacheLock() { return ((IMAPFolder)folder).messageCacheLock; } /** * Get this message's IMAP sequence number. * * ASSERT: This method must be called only when holding the * messageCacheLock. */ protected int getSequenceNumber() { return ((IMAPFolder)folder).messageCache.seqnumOf(getMessageNumber()); } /** * Wrapper around the protected method Message.setMessageNumber() to * make that method accessible to IMAPFolder. */ protected void setMessageNumber(int msgnum) { super.setMessageNumber(msgnum); } protected long getUID() { return uid; } protected void setUID(long uid) { this.uid = uid; } // expose to MessageCache protected void setExpunged(boolean set) { super.setExpunged(set); } // Convenience routine protected void checkExpunged() throws MessageRemovedException { if (expunged) throw new MessageRemovedException(); } /** * Do a NOOP to force any untagged EXPUNGE responses * and then check if this message is expunged. */ protected void forceCheckExpunged() throws MessageRemovedException, FolderClosedException { synchronized (getMessageCacheLock()) { try { getProtocol().noop(); } catch (ConnectionException cex) { throw new FolderClosedException(folder, cex.getMessage()); } catch (ProtocolException pex) { // ignore it } } if (expunged) throw new MessageRemovedException(); } // Return the block size for FETCH requests protected int getFetchBlockSize() { return ((IMAPStore)folder.getStore()).getFetchBlockSize(); } /** * Get the "From" attribute. */ public Address[] getFrom() throws MessagingException { checkExpunged(); loadEnvelope(); return aaclone(envelope.from); } public void setFrom(Address address) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } public void addFrom(Address[] addresses) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get the "Sender" attribute. */ public Address getSender() throws MessagingException { checkExpunged(); loadEnvelope(); if (envelope.sender != null) return (envelope.sender)[0]; // there can be only one sender else return null; } public void setSender(Address address) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get the desired Recipient type. */ public Address[] getRecipients(Message.RecipientType type) throws MessagingException { checkExpunged(); loadEnvelope(); if (type == Message.RecipientType.TO) return aaclone(envelope.to); else if (type == Message.RecipientType.CC) return aaclone(envelope.cc); else if (type == Message.RecipientType.BCC) return aaclone(envelope.bcc); else return super.getRecipients(type); } public void setRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } public void addRecipients(Message.RecipientType type, Address[] addresses) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get the ReplyTo addresses. */ public Address[] getReplyTo() throws MessagingException { checkExpunged(); loadEnvelope(); return aaclone(envelope.replyTo); } public void setReplyTo(Address[] addresses) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get the decoded subject. */ public String getSubject() throws MessagingException { checkExpunged(); if (subject != null) // already cached ? return subject; loadEnvelope(); if (envelope.subject == null) // no subject return null; // Cache and return the decoded value. try { subject = MimeUtility.decodeText(envelope.subject); } catch (UnsupportedEncodingException ex) { subject = envelope.subject; } return subject; } public void setSubject(String subject, String charset) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get the SentDate. */ public Date getSentDate() throws MessagingException { checkExpunged(); loadEnvelope(); if (envelope.date == null) return null; else return new Date(envelope.date.getTime()); } public void setSentDate(Date d) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get the recieved date (INTERNALDATE) */ public Date getReceivedDate() throws MessagingException { checkExpunged(); loadEnvelope(); if (receivedDate == null) return null; else return new Date(receivedDate.getTime()); } /** * Get the message size. <p> * * Note that this returns RFC822.SIZE. That is, it's the * size of the whole message, header and body included. */ public int getSize() throws MessagingException { checkExpunged(); if (size == -1) loadEnvelope(); // XXX - could just fetch the size return size; } /** * Get the total number of lines. <p> * * Returns the "body_fld_lines" field from the * BODYSTRUCTURE. Note that this field is available * only for text/plain and message/rfc822 types */ public int getLineCount() throws MessagingException { checkExpunged(); loadBODYSTRUCTURE(); return bs.lines; } /** * Get the content language. */ public String[] getContentLanguage() throws MessagingException { checkExpunged(); loadBODYSTRUCTURE(); if (bs.language != null) return (String[])(bs.language).clone(); else return null; } public void setContentLanguage(String[] languages) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get the In-Reply-To header. * * @since JavaMail 1.3.3 */ public String getInReplyTo() throws MessagingException { checkExpunged(); loadEnvelope(); return envelope.inReplyTo; } /** * Get the Content-Type. * * Generate this header from the BODYSTRUCTURE. Append parameters * as well. */ public String getContentType() throws MessagingException { checkExpunged(); // If we haven't cached the type yet .. if (type == null) { loadBODYSTRUCTURE(); // generate content-type from BODYSTRUCTURE ContentType ct = new ContentType(bs.type, bs.subtype, bs.cParams); type = ct.toString(); } return type; } /** * Get the Content-Disposition. */ public String getDisposition() throws MessagingException { checkExpunged(); loadBODYSTRUCTURE(); return bs.disposition; } public void setDisposition(String disposition) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get the Content-Transfer-Encoding. */ public String getEncoding() throws MessagingException { checkExpunged(); loadBODYSTRUCTURE(); return bs.encoding; } /** * Get the Content-ID. */ public String getContentID() throws MessagingException { checkExpunged(); loadBODYSTRUCTURE(); return bs.id; } public void setContentID(String cid) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get the Content-MD5. */ public String getContentMD5() throws MessagingException { checkExpunged(); loadBODYSTRUCTURE(); return bs.md5; } public void setContentMD5(String md5) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get the decoded Content-Description. */ public String getDescription() throws MessagingException { checkExpunged(); if (description != null) // cached value ? return description; loadBODYSTRUCTURE(); if (bs.description == null) return null; try { description = MimeUtility.decodeText(bs.description); } catch (UnsupportedEncodingException ex) { description = bs.description; } return description; } public void setDescription(String description, String charset) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get the Message-ID. */ public String getMessageID() throws MessagingException { checkExpunged(); loadEnvelope(); return envelope.messageId; } /** * Get the "filename" Disposition parameter. (Only available in * IMAP4rev1). If thats not available, get the "name" ContentType * parameter. */ public String getFileName() throws MessagingException { checkExpunged(); String filename = null; loadBODYSTRUCTURE(); if (bs.dParams != null) filename = bs.dParams.get("filename"); if (filename == null && bs.cParams != null) filename = bs.cParams.get("name"); return filename; } public void setFileName(String filename) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get all the bytes for this message. Overrides getContentStream() * in MimeMessage. This method is ultimately used by the DataHandler * to obtain the input stream for this message. * * @see javax.mail.internet.MimeMessage#getContentStream */ protected InputStream getContentStream() throws MessagingException { InputStream is = null; boolean pk = getPeek(); // get before acquiring message cache lock // Acquire MessageCacheLock, to freeze seqnum. synchronized(getMessageCacheLock()) { try { IMAPProtocol p = getProtocol(); // This message could be expunged when we were waiting // to acquire the lock ... checkExpunged(); if (p.isREV1() && (getFetchBlockSize() != -1)) // IMAP4rev1 return new IMAPInputStream(this, toSection("TEXT"), bs != null ? bs.size : -1, pk); if (p.isREV1()) { BODY b; if (pk) b = p.peekBody(getSequenceNumber(), toSection("TEXT")); else b = p.fetchBody(getSequenceNumber(), toSection("TEXT")); if (b != null) is = b.getByteArrayInputStream(); } else { RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), "TEXT"); if (rd != null) is = rd.getByteArrayInputStream(); } } catch (ConnectionException cex) { throw new FolderClosedException(folder, cex.getMessage()); } catch (ProtocolException pex) { forceCheckExpunged(); throw new MessagingException(pex.getMessage(), pex); } } if (is == null) throw new MessagingException("No content"); else return is; } /** * Get the DataHandler object for this message. */ public synchronized DataHandler getDataHandler() throws MessagingException { checkExpunged(); if (dh == null) { loadBODYSTRUCTURE(); if (type == null) { // type not yet computed // generate content-type from BODYSTRUCTURE ContentType ct = new ContentType(bs.type, bs.subtype, bs.cParams); type = ct.toString(); } /* Special-case Multipart and Nested content. All other * cases are handled by the superclass. */ if (bs.isMulti()) dh = new DataHandler( new IMAPMultipartDataSource(this, bs.bodies, sectionId, this) ); else if (bs.isNested() && isREV1()) /* Nested messages are handled specially only for * IMAP4rev1. IMAP4 doesn't provide enough support to * FETCH the components of nested messages */ dh = new DataHandler( new IMAPNestedMessage(this, bs.bodies[0], bs.envelope, sectionId == null ? "1" : sectionId + ".1"), type ); } return super.getDataHandler(); } public void setDataHandler(DataHandler content) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Write out the bytes into the given outputstream. */ public void writeTo(OutputStream os) throws IOException, MessagingException { InputStream is = null; boolean pk = getPeek(); // get before acquiring message cache lock // Acquire MessageCacheLock, to freeze seqnum. synchronized(getMessageCacheLock()) { try { IMAPProtocol p = getProtocol(); checkExpunged(); // insure this message is not expunged if (p.isREV1()) { BODY b; if (pk) b = p.peekBody(getSequenceNumber(), sectionId); else b = p.fetchBody(getSequenceNumber(), sectionId); if (b != null) is = b.getByteArrayInputStream(); } else { RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), null); if (rd != null) is = rd.getByteArrayInputStream(); } } catch (ConnectionException cex) { throw new FolderClosedException(folder, cex.getMessage()); } catch (ProtocolException pex) { forceCheckExpunged(); throw new MessagingException(pex.getMessage(), pex); } } if (is == null) throw new MessagingException("No content"); // write out the bytes byte[] bytes = new byte[1024]; int count; while ((count = is.read(bytes)) != -1) os.write(bytes, 0, count); } /** * Get the named header. */ public String[] getHeader(String name) throws MessagingException { checkExpunged(); if (isHeaderLoaded(name)) // already loaded ? return headers.getHeader(name); // Load this particular header InputStream is = null; // Acquire MessageCacheLock, to freeze seqnum. synchronized(getMessageCacheLock()) { try { IMAPProtocol p = getProtocol(); // This message could be expunged when we were waiting // to acquire the lock ... checkExpunged(); if (p.isREV1()) { BODY b = p.peekBody(getSequenceNumber(), toSection("HEADER.FIELDS (" + name + ")") ); if (b != null) is = b.getByteArrayInputStream(); } else { RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), "HEADER.LINES (" + name + ")"); if (rd != null) is = rd.getByteArrayInputStream(); } } catch (ConnectionException cex) { throw new FolderClosedException(folder, cex.getMessage()); } catch (ProtocolException pex) { forceCheckExpunged(); throw new MessagingException(pex.getMessage(), pex); } } // if we get this far without "is" being set, something has gone // wrong; prevent a later NullPointerException and return null here if (is == null) return null; if (headers == null) headers = new InternetHeaders(); headers.load(is); // load this header into the Headers object. setHeaderLoaded(name); // Mark this header as loaded return headers.getHeader(name); } /** * Get the named header. */ public String getHeader(String name, String delimiter) throws MessagingException { checkExpunged(); // force the header to be loaded by invoking getHeader(name) if (getHeader(name) == null) return null; return headers.getHeader(name, delimiter); } public void setHeader(String name, String value) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } public void addHeader(String name, String value) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } public void removeHeader(String name) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get all headers. */ public Enumeration getAllHeaders() throws MessagingException { checkExpunged(); loadHeaders(); return super.getAllHeaders(); } /** * Get matching headers. */ public Enumeration getMatchingHeaders(String[] names) throws MessagingException { checkExpunged(); loadHeaders(); return super.getMatchingHeaders(names); } /** * Get non-matching headers. */ public Enumeration getNonMatchingHeaders(String[] names) throws MessagingException { checkExpunged(); loadHeaders(); return super.getNonMatchingHeaders(names); } public void addHeaderLine(String line) throws MessagingException { throw new IllegalWriteException("IMAPMessage is read-only"); } /** * Get all header-lines. */ public Enumeration getAllHeaderLines() throws MessagingException { checkExpunged(); loadHeaders(); return super.getAllHeaderLines(); } /** * Get all matching header-lines. */ public Enumeration getMatchingHeaderLines(String[] names) throws MessagingException { checkExpunged(); loadHeaders(); return super.getMatchingHeaderLines(names); } /** * Get all non-matching headerlines. */ public Enumeration getNonMatchingHeaderLines(String[] names) throws MessagingException { checkExpunged(); loadHeaders(); return super.getNonMatchingHeaderLines(names); } /** * Get the Flags for this message. */ public synchronized Flags getFlags() throws MessagingException { checkExpunged(); loadFlags(); return super.getFlags(); } /** * Test if the given Flags are set in this message. */ public synchronized boolean isSet(Flags.Flag flag) throws MessagingException { checkExpunged(); loadFlags(); return super.isSet(flag); } /** * Set/Unset the given flags in this message. */ public synchronized void setFlags(Flags flag, boolean set) throws MessagingException { // Acquire MessageCacheLock, to freeze seqnum. synchronized(getMessageCacheLock()) { try { IMAPProtocol p = getProtocol(); checkExpunged(); // Insure that this message is not expunged p.storeFlags(getSequenceNumber(), flag, set); } catch (ConnectionException cex) { throw new FolderClosedException(folder, cex.getMessage()); } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } } } /** * Set whether or not to use the PEEK variant of FETCH when * fetching message content. * * @since JavaMail 1.3.3 */ public synchronized void setPeek(boolean peek) { this.peek = peek; } /** * Get whether or not to use the PEEK variant of FETCH when * fetching message content. * * @since JavaMail 1.3.3 */ public synchronized boolean getPeek() { return peek; } /** * Invalidate cached header and envelope information for this * message. Subsequent accesses of this information will * cause it to be fetched from the server. * * @since JavaMail 1.3.3 */ public synchronized void invalidateHeaders() { headersLoaded = false; loadedHeaders = null; envelope = null; bs = null; receivedDate = null; size = -1; type = null; subject = null; description = null; } /** * The prefetch method. Called from IMAPFolder.fetch() */ static void fetch(IMAPFolder folder, Message[] msgs, FetchProfile fp) throws MessagingException { /* This class implements the test to be done on each * message in the folder. The test is to check whether the * message has already cached all the items requested in the * FetchProfile. If any item is missing, the test succeeds and * breaks out. */ class FetchProfileCondition implements Utility.Condition { private boolean needEnvelope = false; private boolean needFlags = false; private boolean needBodyStructure = false; private boolean needUID = false; private boolean needHeaders = false; private boolean needSize = false; private String[] hdrs = null; public FetchProfileCondition(FetchProfile fp) { if (fp.contains(FetchProfile.Item.ENVELOPE)) needEnvelope = true; if (fp.contains(FetchProfile.Item.FLAGS)) needFlags = true; if (fp.contains(FetchProfile.Item.CONTENT_INFO)) needBodyStructure = true; if (fp.contains(UIDFolder.FetchProfileItem.UID)) needUID = true; if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS)) needHeaders = true; if (fp.contains(IMAPFolder.FetchProfileItem.SIZE)) needSize = true; hdrs = fp.getHeaderNames(); } // The actual test. public boolean test(IMAPMessage m) { if (needEnvelope && m._getEnvelope() == null) return true; // no envelope if (needFlags && m._getFlags() == null) return true; // no flags if (needBodyStructure && m._getBodyStructure() == null) return true; // no BODYSTRUCTURE if (needUID && m.getUID() == -1) // no UID return true; if (needHeaders && !m.areHeadersLoaded()) // no headers return true; if (needSize && m.size == -1) // no size return true; // Is the desired header present ? for (int i = 0; i < hdrs.length; i++) { if (!m.isHeaderLoaded(hdrs[i])) return true; // Nope, return } return false; } } StringBuffer command = new StringBuffer(); boolean first = true; boolean allHeaders = false; if (fp.contains(FetchProfile.Item.ENVELOPE)) { command.append(EnvelopeCmd); first = false; } if (fp.contains(FetchProfile.Item.FLAGS)) { command.append(first ? "FLAGS" : " FLAGS"); first = false; } if (fp.contains(FetchProfile.Item.CONTENT_INFO)) { command.append(first ? "BODYSTRUCTURE" : " BODYSTRUCTURE"); first = false; } if (fp.contains(UIDFolder.FetchProfileItem.UID)) { command.append(first ? "UID" : " UID"); first = false; } if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS)) { allHeaders = true; if (folder.protocol.isREV1()) command.append(first ? "BODY.PEEK[HEADER]" : " BODY.PEEK[HEADER]"); else command.append(first ? "RFC822.HEADER" : " RFC822.HEADER"); first = false; } if (fp.contains(IMAPFolder.FetchProfileItem.SIZE)) { command.append(first ? "RFC822.SIZE" : " RFC822.SIZE"); first = false; } // if we're not fetching all headers, fetch individual headers String[] hdrs = null; if (!allHeaders) { hdrs = fp.getHeaderNames(); if (hdrs.length > 0) { if (!first) command.append(" "); command.append(craftHeaderCmd(folder.protocol, hdrs)); } } Utility.Condition condition = new FetchProfileCondition(fp); // Acquire the Folder's MessageCacheLock. synchronized(folder.messageCacheLock) { // Apply the test, and get the sequence-number set for // the messages that need to be prefetched. MessageSet[] msgsets = Utility.toMessageSet(msgs, condition); if (msgsets == null) // We already have what we need. return; Response[] r = null; Vector v = new Vector(); // to collect non-FETCH responses & // unsolicited FETCH FLAG responses try { r = folder.protocol.fetch(msgsets, command.toString()); } catch (ConnectionException cex) { throw new FolderClosedException(folder, cex.getMessage()); } catch (CommandFailedException cfx) { // Ignore these, as per RFC 2180 } catch (ProtocolException pex) { throw new MessagingException(pex.getMessage(), pex); } if (r == null) return; for (int i = 0; i < r.length; i++) { if (r[i] == null) continue; if (!(r[i] instanceof FetchResponse)) { v.addElement(r[i]); // Unsolicited Non-FETCH response continue; } // Got a FetchResponse. FetchResponse f = (FetchResponse)r[i]; // Get the corresponding message. IMAPMessage msg = folder.getMessageBySeqNumber(f.getNumber()); int count = f.getItemCount(); boolean unsolicitedFlags = false; for (int j = 0; j < count; j++) { Item item = f.getItem(j); // Check for the FLAGS item if (item instanceof Flags) { if (!fp.contains(FetchProfile.Item.FLAGS) || msg == null) // Ok, Unsolicited FLAGS update. unsolicitedFlags = true; else msg.flags = (Flags)item; } // Check for ENVELOPE items else if (item instanceof ENVELOPE) msg.envelope = (ENVELOPE)item; else if (item instanceof INTERNALDATE) msg.receivedDate = ((INTERNALDATE)item).getDate(); else if (item instanceof RFC822SIZE) msg.size = ((RFC822SIZE)item).size; // Check for the BODYSTRUCTURE item else if (item instanceof BODYSTRUCTURE) msg.bs = (BODYSTRUCTURE)item; // Check for the UID item else if (item instanceof UID) { UID u = (UID)item; msg.uid = u.uid; // set uid // add entry into uid table if (folder.uidTable == null) folder.uidTable = new Hashtable(); folder.uidTable.put(new Long(u.uid), msg); } // Check for header items else if (item instanceof RFC822DATA || item instanceof BODY) { InputStream headerStream; if (item instanceof RFC822DATA) // IMAP4 headerStream = ((RFC822DATA)item).getByteArrayInputStream(); else // IMAP4rev1 headerStream = ((BODY)item).getByteArrayInputStream(); // Load the obtained headers. InternetHeaders h = new InternetHeaders(); h.load(headerStream); if (msg.headers == null || allHeaders) msg.headers = h; else { /* * This is really painful. A second fetch * of the same headers (which might occur because * a new header was added to the set requested) * will return headers we already know about. * In this case, only load the headers we haven't * seen before to avoid adding duplicates of * headers we already have. */ Enumeration e = h.getAllHeaders(); while (e.hasMoreElements()) { Header he = (Header)e.nextElement(); if (!msg.isHeaderLoaded(he.getName())) msg.headers.addHeader( he.getName(), he.getValue()); } } // if we asked for all headers, assume we got them if (allHeaders) msg.setHeadersLoaded(true); else { // Mark all headers we asked for as 'loaded' for (int k = 0; k < hdrs.length; k++) msg.setHeaderLoaded(hdrs[k]); } } } // If this response contains any unsolicited FLAGS // add it to the unsolicited response vector if (unsolicitedFlags) v.addElement(f); } // Dispatch any unsolicited responses int size = v.size(); if (size != 0) { Response[] responses = new Response[size]; v.copyInto(responses); folder.handleResponses(responses); } } // Release messageCacheLock } /* * Load the Envelope for this message. */ private synchronized void loadEnvelope() throws MessagingException { if (envelope != null) // already loaded return; Response[] r = null; // Acquire MessageCacheLock, to freeze seqnum. synchronized(getMessageCacheLock()) { try { IMAPProtocol p = getProtocol(); checkExpunged(); // Insure that this message is not expunged int seqnum = getSequenceNumber(); r = p.fetch(seqnum, EnvelopeCmd); for (int i = 0; i < r.length; i++) { // If this response is NOT a FetchResponse or if it does // not match our seqnum, skip. if (r[i] == null || !(r[i] instanceof FetchResponse) || ((FetchResponse)r[i]).getNumber() != seqnum) continue; FetchResponse f = (FetchResponse)r[i]; // Look for the Envelope items. int count = f.getItemCount(); for (int j = 0; j < count; j++) { Item item = f.getItem(j); if (item instanceof ENVELOPE) envelope = (ENVELOPE)item; else if (item instanceof INTERNALDATE) receivedDate = ((INTERNALDATE)item).getDate(); else if (item instanceof RFC822SIZE) size = ((RFC822SIZE)item).size; } } // ((IMAPFolder)folder).handleResponses(r); p.notifyResponseHandlers(r); p.handleResult(r[r.length - 1]); } catch (ConnectionException cex) { throw new FolderClosedException(folder, cex.getMessage()); } catch (ProtocolException pex) { forceCheckExpunged(); throw new MessagingException(pex.getMessage(), pex); } } // Release MessageCacheLock if (envelope == null) throw new MessagingException("Failed to load IMAP envelope"); } private static String craftHeaderCmd(IMAPProtocol p, String[] hdrs) { StringBuffer sb; if (p.isREV1()) sb = new StringBuffer("BODY.PEEK[HEADER.FIELDS ("); else sb = new StringBuffer("RFC822.HEADER.LINES ("); for (int i = 0; i < hdrs.length; i++) { if (i > 0) sb.append(" "); sb.append(hdrs[i]); } if (p.isREV1()) sb.append(")]"); else sb.append(")"); return sb.toString(); } /* * Load the BODYSTRUCTURE */ private synchronized void loadBODYSTRUCTURE() throws MessagingException { if (bs != null) // already loaded return; // Acquire MessageCacheLock, to freeze seqnum. synchronized(getMessageCacheLock()) { try { IMAPProtocol p = getProtocol(); // This message could be expunged when we were waiting // to acquire the lock ... checkExpunged(); bs = p.fetchBodyStructure(getSequenceNumber()); } catch (ConnectionException cex) { throw new FolderClosedException(folder, cex.getMessage()); } catch (ProtocolException pex) { forceCheckExpunged(); throw new MessagingException(pex.getMessage(), pex); } if (bs == null) { // if the FETCH is successful, we should always get a // BODYSTRUCTURE, but some servers fail to return it // if the message has been expunged forceCheckExpunged(); throw new MessagingException("Unable to load BODYSTRUCTURE"); } } } /* * Load all headers. */ private synchronized void loadHeaders() throws MessagingException { if (headersLoaded) return; InputStream is = null; // Acquire MessageCacheLock, to freeze seqnum. synchronized (getMessageCacheLock()) { try { IMAPProtocol p = getProtocol(); // This message could be expunged when we were waiting // to acquire the lock ... checkExpunged(); if (p.isREV1()) { BODY b = p.peekBody(getSequenceNumber(), toSection("HEADER")); if (b != null) is = b.getByteArrayInputStream(); } else { RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), "HEADER"); if (rd != null) is = rd.getByteArrayInputStream(); } } catch (ConnectionException cex) { throw new FolderClosedException(folder, cex.getMessage()); } catch (ProtocolException pex) { forceCheckExpunged(); throw new MessagingException(pex.getMessage(), pex); } } // Release MessageCacheLock if (is == null) throw new MessagingException("Cannot load header"); headers = new InternetHeaders(is); headersLoaded = true; } /* * Load this message's Flags */ private synchronized void loadFlags() throws MessagingException { if (flags != null) return; // Acquire MessageCacheLock, to freeze seqnum. synchronized(getMessageCacheLock()) { try { IMAPProtocol p = getProtocol(); // This message could be expunged when we were waiting // to acquire the lock ... checkExpunged(); flags = p.fetchFlags(getSequenceNumber()); // make sure flags is always set, even if server is broken if (flags == null) flags = new Flags(); } catch (ConnectionException cex) { throw new FolderClosedException(folder, cex.getMessage()); } catch (ProtocolException pex) { forceCheckExpunged(); throw new MessagingException(pex.getMessage(), pex); } } // Release MessageCacheLock } /* * Are all headers loaded? */ private synchronized boolean areHeadersLoaded() { return headersLoaded; } /* * Set whether all headers are loaded. */ private synchronized void setHeadersLoaded(boolean loaded) { headersLoaded = loaded; } /* * Check if the given header was ever loaded from the server */ private synchronized boolean isHeaderLoaded(String name) { if (headersLoaded) // All headers for this message have been loaded return true; return (loadedHeaders != null) ? loadedHeaders.containsKey(name.toUpperCase(Locale.ENGLISH)) : false; } /* * Mark that the given headers have been loaded from the server. */ private synchronized void setHeaderLoaded(String name) { if (loadedHeaders == null) loadedHeaders = new Hashtable(1); loadedHeaders.put(name.toUpperCase(Locale.ENGLISH), name); } /* * Convert the given FETCH item identifier to the approriate * section-string for this message. */ private String toSection(String what) { if (sectionId == null) return what; else return sectionId + "." + what; } /* * Clone an array of InternetAddresses. */ private InternetAddress[] aaclone(InternetAddress[] aa) { if (aa == null) return null; else return (InternetAddress[])aa.clone(); } private Flags _getFlags() { return flags; } private ENVELOPE _getEnvelope() { return envelope; } private BODYSTRUCTURE _getBodyStructure() { return bs; } /*********************************************************** * accessor routines to make available certain private/protected * fields to other classes in this package. ***********************************************************/ /* * Called by IMAPFolder. * Must not be synchronized. */ void _setFlags(Flags flags) { this.flags = flags; } /* * Called by IMAPNestedMessage. */ Session _getSession() { return session; } }