mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-16 22:45:04 -05:00
c79ea226a5
couple of try blocks that only caught throws we didn't throw. IntelliJ optimization.
656 lines
17 KiB
Java
656 lines
17 KiB
Java
|
|
package com.fsck.k9.mail.internet;
|
|
|
|
import com.fsck.k9.mail.*;
|
|
import com.fsck.k9.mail.store.UnavailableStorageException;
|
|
|
|
import org.apache.james.mime4j.BodyDescriptor;
|
|
import org.apache.james.mime4j.ContentHandler;
|
|
import org.apache.james.mime4j.EOLConvertingInputStream;
|
|
import org.apache.james.mime4j.MimeStreamParser;
|
|
import org.apache.james.mime4j.field.DateTimeField;
|
|
import org.apache.james.mime4j.field.Field;
|
|
|
|
import java.io.*;
|
|
import java.text.SimpleDateFormat;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* An implementation of Message that stores all of it's metadata in RFC 822 and
|
|
* RFC 2045 style headers.
|
|
*/
|
|
public class MimeMessage extends Message
|
|
{
|
|
protected MimeHeader mHeader = new MimeHeader();
|
|
protected Address[] mFrom;
|
|
protected Address[] mTo;
|
|
protected Address[] mCc;
|
|
protected Address[] mBcc;
|
|
protected Address[] mReplyTo;
|
|
|
|
protected String mMessageId;
|
|
protected String[] mReferences;
|
|
protected String[] mInReplyTo;
|
|
|
|
protected Date mSentDate;
|
|
protected SimpleDateFormat mDateFormat;
|
|
|
|
protected Body mBody;
|
|
protected int mSize;
|
|
|
|
public MimeMessage()
|
|
{
|
|
}
|
|
|
|
|
|
/**
|
|
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
|
|
*
|
|
* @param in
|
|
* @throws IOException
|
|
* @throws MessagingException
|
|
*/
|
|
public MimeMessage(InputStream in) throws IOException, MessagingException
|
|
{
|
|
parse(in);
|
|
}
|
|
|
|
protected void parse(InputStream in) throws IOException, MessagingException
|
|
{
|
|
mHeader.clear();
|
|
mFrom = null;
|
|
mTo = null;
|
|
mCc = null;
|
|
mBcc = null;
|
|
mReplyTo = null;
|
|
|
|
mMessageId = null;
|
|
mReferences = null;
|
|
mInReplyTo = null;
|
|
|
|
mSentDate = null;
|
|
|
|
mBody = null;
|
|
|
|
MimeStreamParser parser = new MimeStreamParser();
|
|
parser.setContentHandler(new MimeMessageBuilder());
|
|
parser.parse(new EOLConvertingInputStream(in));
|
|
}
|
|
|
|
@Override
|
|
public Date getSentDate()
|
|
{
|
|
if (mSentDate == null)
|
|
{
|
|
try
|
|
{
|
|
DateTimeField field = (DateTimeField)Field.parse("Date: "
|
|
+ MimeUtility.unfoldAndDecode(getFirstHeader("Date")));
|
|
mSentDate = field.getDate();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
|
|
}
|
|
}
|
|
return mSentDate;
|
|
}
|
|
|
|
/**
|
|
* Sets the sent date object member as well as *adds* the 'Date' header
|
|
* instead of setting it (for performance reasons).
|
|
*
|
|
* @see #mSentDate
|
|
* @param sentDate
|
|
* @throws com.fsck.k9.mail.MessagingException
|
|
*/
|
|
public void addSentDate(Date sentDate) throws MessagingException
|
|
{
|
|
if (mDateFormat == null)
|
|
{
|
|
mDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
|
|
}
|
|
addHeader("Date", mDateFormat.format(sentDate));
|
|
setInternalSentDate(sentDate);
|
|
}
|
|
|
|
@Override
|
|
public void setSentDate(Date sentDate) throws MessagingException
|
|
{
|
|
removeHeader("Date");
|
|
addSentDate(sentDate);
|
|
}
|
|
|
|
public void setInternalSentDate(Date sentDate) {
|
|
this.mSentDate = sentDate;
|
|
}
|
|
|
|
@Override
|
|
public String getContentType() throws MessagingException
|
|
{
|
|
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
|
if (contentType == null)
|
|
{
|
|
return "text/plain";
|
|
}
|
|
else
|
|
{
|
|
return contentType.toLowerCase();
|
|
}
|
|
}
|
|
|
|
public String getDisposition() throws MessagingException
|
|
{
|
|
String contentDisposition = getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
|
|
if (contentDisposition == null)
|
|
{
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
return contentDisposition;
|
|
}
|
|
}
|
|
public String getContentId() throws MessagingException
|
|
{
|
|
return null;
|
|
}
|
|
public String getMimeType() throws MessagingException
|
|
{
|
|
return MimeUtility.getHeaderParameter(getContentType(), null);
|
|
}
|
|
|
|
public int getSize() {
|
|
return mSize;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of the given recipient type from this message. If no addresses are
|
|
* found the method returns an empty array.
|
|
*/
|
|
@Override
|
|
public Address[] getRecipients(RecipientType type) throws MessagingException
|
|
{
|
|
if (type == RecipientType.TO)
|
|
{
|
|
if (mTo == null)
|
|
{
|
|
mTo = Address.parse(MimeUtility.unfold(getFirstHeader("To")));
|
|
}
|
|
return mTo;
|
|
}
|
|
else if (type == RecipientType.CC)
|
|
{
|
|
if (mCc == null)
|
|
{
|
|
mCc = Address.parse(MimeUtility.unfold(getFirstHeader("CC")));
|
|
}
|
|
return mCc;
|
|
}
|
|
else if (type == RecipientType.BCC)
|
|
{
|
|
if (mBcc == null)
|
|
{
|
|
mBcc = Address.parse(MimeUtility.unfold(getFirstHeader("BCC")));
|
|
}
|
|
return mBcc;
|
|
}
|
|
else
|
|
{
|
|
throw new MessagingException("Unrecognized recipient type.");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setRecipients(RecipientType type, Address[] addresses) throws MessagingException
|
|
{
|
|
if (type == RecipientType.TO)
|
|
{
|
|
if (addresses == null || addresses.length == 0)
|
|
{
|
|
removeHeader("To");
|
|
this.mTo = null;
|
|
}
|
|
else
|
|
{
|
|
setHeader("To", Address.toEncodedString(addresses));
|
|
this.mTo = addresses;
|
|
}
|
|
}
|
|
else if (type == RecipientType.CC)
|
|
{
|
|
if (addresses == null || addresses.length == 0)
|
|
{
|
|
removeHeader("CC");
|
|
this.mCc = null;
|
|
}
|
|
else
|
|
{
|
|
setHeader("CC", Address.toEncodedString(addresses));
|
|
this.mCc = addresses;
|
|
}
|
|
}
|
|
else if (type == RecipientType.BCC)
|
|
{
|
|
if (addresses == null || addresses.length == 0)
|
|
{
|
|
removeHeader("BCC");
|
|
this.mBcc = null;
|
|
}
|
|
else
|
|
{
|
|
setHeader("BCC", Address.toEncodedString(addresses));
|
|
this.mBcc = addresses;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new MessagingException("Unrecognized recipient type.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the unfolded, decoded value of the Subject header.
|
|
*/
|
|
@Override
|
|
public String getSubject() {
|
|
return MimeUtility.unfoldAndDecode(getFirstHeader("Subject"));
|
|
}
|
|
|
|
@Override
|
|
public void setSubject(String subject) throws MessagingException
|
|
{
|
|
setHeader("Subject", subject);
|
|
}
|
|
|
|
@Override
|
|
public Address[] getFrom() {
|
|
if (mFrom == null)
|
|
{
|
|
String list = MimeUtility.unfold(getFirstHeader("From"));
|
|
if (list == null || list.length() == 0)
|
|
{
|
|
list = MimeUtility.unfold(getFirstHeader("Sender"));
|
|
}
|
|
mFrom = Address.parse(list);
|
|
}
|
|
return mFrom;
|
|
}
|
|
|
|
@Override
|
|
public void setFrom(Address from) throws MessagingException
|
|
{
|
|
if (from != null)
|
|
{
|
|
setHeader("From", from.toEncodedString());
|
|
this.mFrom = new Address[]
|
|
{
|
|
from
|
|
};
|
|
}
|
|
else
|
|
{
|
|
this.mFrom = null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Address[] getReplyTo() {
|
|
if (mReplyTo == null)
|
|
{
|
|
mReplyTo = Address.parse(MimeUtility.unfold(getFirstHeader("Reply-to")));
|
|
}
|
|
return mReplyTo;
|
|
}
|
|
|
|
@Override
|
|
public void setReplyTo(Address[] replyTo) throws MessagingException
|
|
{
|
|
if (replyTo == null || replyTo.length == 0)
|
|
{
|
|
removeHeader("Reply-to");
|
|
mReplyTo = null;
|
|
}
|
|
else
|
|
{
|
|
setHeader("Reply-to", Address.toEncodedString(replyTo));
|
|
mReplyTo = replyTo;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getMessageId() throws MessagingException
|
|
{
|
|
if (mMessageId == null)
|
|
{
|
|
mMessageId = getFirstHeader("Message-ID");
|
|
}
|
|
if (mMessageId == null) // even after checking the header
|
|
{
|
|
setMessageId(generateMessageId());
|
|
}
|
|
return mMessageId;
|
|
}
|
|
|
|
private String generateMessageId()
|
|
{
|
|
return "<"+UUID.randomUUID().toString()+"@email.android.com>";
|
|
}
|
|
|
|
public void setMessageId(String messageId) throws UnavailableStorageException
|
|
{
|
|
setHeader("Message-ID", messageId);
|
|
mMessageId = messageId;
|
|
}
|
|
|
|
@Override
|
|
public void setInReplyTo(String inReplyTo) throws MessagingException
|
|
{
|
|
setHeader("In-Reply-To", inReplyTo);
|
|
}
|
|
|
|
@Override
|
|
public String[] getReferences() throws MessagingException
|
|
{
|
|
if (mReferences == null)
|
|
{
|
|
mReferences = getHeader("References");
|
|
}
|
|
return mReferences;
|
|
}
|
|
|
|
@Override
|
|
public void setReferences(String references) throws MessagingException
|
|
{
|
|
/*
|
|
* Make sure the References header doesn't exceed the maximum header
|
|
* line length and won't get (Q-)encoded later. Otherwise some clients
|
|
* will break threads apart.
|
|
*
|
|
* For more information see issue 1559.
|
|
*/
|
|
|
|
// Make sure separator is SPACE to prevent Q-encoding when TAB is encountered
|
|
references = references.replaceAll("\\s+", " ");
|
|
|
|
/*
|
|
* NOTE: Usually the maximum header line is 998 + CRLF = 1000 characters.
|
|
* But at least one implementations seems to have problems with 998
|
|
* characters, so we adjust for that fact.
|
|
*/
|
|
final int limit = 1000 - 2 /* CRLF */ - 12 /* "References: " */ - 1 /* Off-by-one bugs */;
|
|
final int originalLength = references.length();
|
|
if (originalLength >= limit)
|
|
{
|
|
// Find start of first reference
|
|
final int start = references.indexOf('<');
|
|
|
|
// First reference + SPACE
|
|
final String firstReference = references.substring(start,
|
|
references.indexOf('<', start + 1));
|
|
|
|
// Find longest tail
|
|
final String tail = references.substring(references.indexOf('<',
|
|
firstReference.length() + originalLength - limit));
|
|
|
|
references = firstReference + tail;
|
|
}
|
|
setHeader("References", references);
|
|
}
|
|
|
|
@Override
|
|
public void saveChanges() throws MessagingException
|
|
{
|
|
throw new MessagingException("saveChanges not yet implemented");
|
|
}
|
|
|
|
@Override
|
|
public Body getBody() {
|
|
return mBody;
|
|
}
|
|
|
|
@Override
|
|
public void setBody(Body body) throws MessagingException
|
|
{
|
|
this.mBody = body;
|
|
setHeader("MIME-Version", "1.0");
|
|
if (body instanceof Multipart)
|
|
{
|
|
Multipart multipart = ((Multipart)body);
|
|
multipart.setParent(this);
|
|
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
|
|
}
|
|
else if (body instanceof TextBody)
|
|
{
|
|
setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n charset=utf-8",
|
|
getMimeType()));
|
|
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "quoted-printable");
|
|
}
|
|
}
|
|
|
|
protected String getFirstHeader(String name)
|
|
{
|
|
return mHeader.getFirstHeader(name);
|
|
}
|
|
|
|
@Override
|
|
public void addHeader(String name, String value) throws UnavailableStorageException
|
|
{
|
|
mHeader.addHeader(name, value);
|
|
}
|
|
|
|
@Override
|
|
public void setHeader(String name, String value) throws UnavailableStorageException
|
|
{
|
|
mHeader.setHeader(name, value);
|
|
}
|
|
|
|
@Override
|
|
public String[] getHeader(String name) throws UnavailableStorageException
|
|
{
|
|
return mHeader.getHeader(name);
|
|
}
|
|
|
|
@Override
|
|
public void removeHeader(String name) throws UnavailableStorageException
|
|
{
|
|
mHeader.removeHeader(name);
|
|
}
|
|
|
|
@Override
|
|
public Set<String> getHeaderNames() throws UnavailableStorageException
|
|
{
|
|
return mHeader.getHeaderNames();
|
|
}
|
|
|
|
public void writeTo(OutputStream out) throws IOException, MessagingException
|
|
{
|
|
|
|
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
|
mHeader.writeTo(out);
|
|
writer.write("\r\n");
|
|
writer.flush();
|
|
if (mBody != null)
|
|
{
|
|
mBody.writeTo(out);
|
|
}
|
|
}
|
|
|
|
public InputStream getInputStream() throws MessagingException
|
|
{
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void setEncoding(String encoding) throws UnavailableStorageException
|
|
{
|
|
if (mBody instanceof Multipart)
|
|
{
|
|
((Multipart)mBody).setEncoding(encoding);
|
|
}
|
|
else if (mBody instanceof TextBody)
|
|
{
|
|
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
|
|
((TextBody)mBody).setEncoding(encoding);
|
|
}
|
|
}
|
|
|
|
class MimeMessageBuilder implements ContentHandler
|
|
{
|
|
private Stack<Object> stack = new Stack<Object>();
|
|
|
|
public MimeMessageBuilder()
|
|
{
|
|
}
|
|
|
|
private void expect(Class<?> c)
|
|
{
|
|
if (!c.isInstance(stack.peek()))
|
|
{
|
|
throw new IllegalStateException("Internal stack error: " + "Expected '"
|
|
+ c.getName() + "' found '" + stack.peek().getClass().getName() + "'");
|
|
}
|
|
}
|
|
|
|
public void startMessage()
|
|
{
|
|
if (stack.isEmpty())
|
|
{
|
|
stack.push(MimeMessage.this);
|
|
}
|
|
else
|
|
{
|
|
expect(Part.class);
|
|
try
|
|
{
|
|
MimeMessage m = new MimeMessage();
|
|
((Part)stack.peek()).setBody(m);
|
|
stack.push(m);
|
|
}
|
|
catch (MessagingException me)
|
|
{
|
|
throw new Error(me);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void endMessage()
|
|
{
|
|
expect(MimeMessage.class);
|
|
stack.pop();
|
|
}
|
|
|
|
public void startHeader()
|
|
{
|
|
expect(Part.class);
|
|
}
|
|
|
|
public void field(String fieldData)
|
|
{
|
|
expect(Part.class);
|
|
try
|
|
{
|
|
String[] tokens = fieldData.split(":", 2);
|
|
((Part)stack.peek()).addHeader(tokens[0], tokens[1].trim());
|
|
}
|
|
catch (MessagingException me)
|
|
{
|
|
throw new Error(me);
|
|
}
|
|
}
|
|
|
|
public void endHeader()
|
|
{
|
|
expect(Part.class);
|
|
}
|
|
|
|
public void startMultipart(BodyDescriptor bd)
|
|
{
|
|
expect(Part.class);
|
|
|
|
Part e = (Part)stack.peek();
|
|
try
|
|
{
|
|
MimeMultipart multiPart = new MimeMultipart(e.getContentType());
|
|
e.setBody(multiPart);
|
|
stack.push(multiPart);
|
|
}
|
|
catch (MessagingException me)
|
|
{
|
|
throw new Error(me);
|
|
}
|
|
}
|
|
|
|
public void body(BodyDescriptor bd, InputStream in) throws IOException
|
|
{
|
|
expect(Part.class);
|
|
Body body = MimeUtility.decodeBody(in, bd.getTransferEncoding());
|
|
try
|
|
{
|
|
((Part)stack.peek()).setBody(body);
|
|
}
|
|
catch (MessagingException me)
|
|
{
|
|
throw new Error(me);
|
|
}
|
|
}
|
|
|
|
public void endMultipart()
|
|
{
|
|
stack.pop();
|
|
}
|
|
|
|
public void startBodyPart()
|
|
{
|
|
expect(MimeMultipart.class);
|
|
|
|
try
|
|
{
|
|
MimeBodyPart bodyPart = new MimeBodyPart();
|
|
((MimeMultipart)stack.peek()).addBodyPart(bodyPart);
|
|
stack.push(bodyPart);
|
|
}
|
|
catch (MessagingException me)
|
|
{
|
|
throw new Error(me);
|
|
}
|
|
}
|
|
|
|
public void endBodyPart()
|
|
{
|
|
expect(BodyPart.class);
|
|
stack.pop();
|
|
}
|
|
|
|
public void epilogue(InputStream is) throws IOException
|
|
{
|
|
expect(MimeMultipart.class);
|
|
StringBuffer sb = new StringBuffer();
|
|
int b;
|
|
while ((b = is.read()) != -1)
|
|
{
|
|
sb.append((char)b);
|
|
}
|
|
// ((Multipart) stack.peek()).setEpilogue(sb.toString());
|
|
}
|
|
|
|
public void preamble(InputStream is) throws IOException
|
|
{
|
|
expect(MimeMultipart.class);
|
|
StringBuffer sb = new StringBuffer();
|
|
int b;
|
|
while ((b = is.read()) != -1)
|
|
{
|
|
sb.append((char)b);
|
|
}
|
|
((MimeMultipart)stack.peek()).setPreamble(sb.toString());
|
|
|
|
}
|
|
|
|
public void raw(InputStream is) throws IOException
|
|
{
|
|
throw new UnsupportedOperationException("Not supported");
|
|
}
|
|
}
|
|
}
|