k-9/k9mail-library/src/main/java/com/fsck/k9/mail/Message.java

271 lines
8.1 KiB
Java
Raw Normal View History

package com.fsck.k9.mail;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
2014-10-05 07:08:55 -04:00
import java.util.EnumSet;
import java.util.Set;
import android.util.Log;
import com.fsck.k9.mail.filter.CountingOutputStream;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
2014-12-16 06:51:52 -05:00
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
Recursively convert attachments of type message/rfc822 to 7bit if necessary. The preceding commit resulted in attachments of type message/rfc822 being sent with 8bit encoding even when the SMTP server did not support 8BITMIME. This commit assures that messages will be converted to 7bit when necessary. A new interface CompositeBody was created that extends Body, and classes Message and Multipart were changed from implementing Body to CompositeBody. Additional classes BinaryTempFileMessageBody and LocalAttachmentMessageBody were created (by extending BinaryTempFileBody and LocalAttachmentBody, respectively), and they too implement CompositeBody. A CompositeBody is a Body containing a composite-type that can contain subparts that may require recursive processing when converting from 8bit to 7bit. The Part to which a CompositeBody belongs is only permitted to use 8bit or 7bit encoding for the CompositeBody. Previously, a Message was created so that it was 7bit clean by default (even though that meant base64 encoding all attachments, including messages). Then, if the SMTP server supported 8BITMIME, Message.setEncoding("8bit") was called so that bodies of type TextBody would been transmitted using 8bit encoding rather than quoted-printable. Now, messages are created with 8bit encoding by default. Then, if the SMTP server does not support 8BITMIME, Message.setUsing7bitTransport is called to recursively convert the message and its subparts to 7bit. The method setUsing7bitTransport was added to the interfaces Part and CompositeBody. setEncoding no longer iterates over parts in Multipart. That task belongs to setUsing7bitTransport, which may in turn call setEncoding on the parts. MimeUtility.getEncodingforType was created as a helper function for choosing a default encoding that should be used for a given MIME type when an attachment is added to a message (either while composing or when retrieving from LocalStore). setEncoding was implemented in MimeBodyPart to assure that the encoding set in the Part's headers was the same as set for the Part's Body. (The method already existed in MimeMessage, which has similarities with MimeBodyPart.) MimeMessage.parse(InputStream in, boolean recurse) was implemented so that the parser could be told to recursively process nested messages read from the InputStream, thus giving access to all subparts at any level that may need to be converted from 8bit to 7bit.
2013-09-02 23:49:28 -04:00
public abstract class Message implements Part, CompositeBody {
public enum RecipientType {
TO, CC, BCC,
}
protected String mUid;
2014-10-05 07:08:55 -04:00
private Set<Flag> mFlags = EnumSet.noneOf(Flag.class);
private Date mInternalDate;
protected Folder mFolder;
public boolean olderThan(Date earliestDate) {
if (earliestDate == null) {
return false;
}
Date myDate = getSentDate();
if (myDate == null) {
myDate = getInternalDate();
}
if (myDate != null) {
return myDate.before(earliestDate);
}
return false;
}
2010-05-03 09:46:55 -04:00
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof Message)) {
return false;
}
Message other = (Message)o;
2014-12-12 10:16:38 -05:00
return (getUid().equals(other.getUid())
&& getFolder().getName().equals(other.getFolder().getName()));
}
2010-05-11 22:51:59 -04:00
2010-05-03 09:46:55 -04:00
@Override
public int hashCode() {
2010-05-03 09:46:55 -04:00
final int MULTIPLIER = 31;
int result = 1;
result = MULTIPLIER * result + mFolder.getName().hashCode();
result = MULTIPLIER * result + mUid.hashCode();
return result;
}
2010-05-11 22:51:59 -04:00
public String getUid() {
return mUid;
}
public void setUid(String uid) {
this.mUid = uid;
}
public Folder getFolder() {
return mFolder;
}
public abstract String getSubject();
public abstract void setSubject(String subject) throws MessagingException;
public Date getInternalDate() {
return mInternalDate;
}
public void setInternalDate(Date internalDate) {
this.mInternalDate = internalDate;
}
public abstract Date getSentDate();
public abstract void setSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException;
public abstract Address[] getRecipients(RecipientType type) throws MessagingException;
public abstract void setRecipients(RecipientType type, Address[] addresses)
throws MessagingException;
public void setRecipient(RecipientType type, Address address) throws MessagingException {
setRecipients(type, new Address[] {
address
});
}
public abstract Address[] getFrom();
public abstract void setFrom(Address from) throws MessagingException;
public abstract Address[] getReplyTo();
public abstract void setReplyTo(Address[] from) throws MessagingException;
public abstract String getMessageId() throws MessagingException;
public abstract void setInReplyTo(String inReplyTo) throws MessagingException;
public abstract String[] getReferences() throws MessagingException;
public abstract void setReferences(String references) throws MessagingException;
public abstract Set<String> getHeaderNames() throws MessagingException;
public abstract long getId();
public abstract String getPreview();
public abstract boolean hasAttachments();
public abstract int getSize();
2013-01-05 07:20:46 -05:00
/*
* calculateContentPreview
* Takes a plain text message body as a string.
* Returns a message summary as a string suitable for showing in a message list
*
* A message summary should be about the first 160 characters
* of unique text written by the message sender
* Quoted text, "On $date" and so on will be stripped out.
* All newlines and whitespace will be compressed.
*
*/
public static String calculateContentPreview(String text) {
if (text == null) {
return null;
}
2013-01-05 07:20:46 -05:00
// Only look at the first 8k of a message when calculating
// the preview. This should avoid unnecessary
// memory usage on large messages
if (text.length() > 8192) {
text = text.substring(0, 8192);
}
2013-01-07 04:39:08 -05:00
// Remove (correctly delimited by '-- \n') signatures
text = text.replaceAll("(?ms)^-- [\\r\\n]+.*", "");
2013-01-05 07:20:46 -05:00
// try to remove lines of dashes in the preview
text = text.replaceAll("(?m)^----.*?$", "");
// remove quoted text from the preview
text = text.replaceAll("(?m)^[#>].*$", "");
// Remove a common quote header from the preview
text = text.replaceAll("(?m)^On .*wrote.?$", "");
// Remove a more generic quote header from the preview
text = text.replaceAll("(?m)^.*\\w+:$", "");
// Remove horizontal rules.
text = text.replaceAll("\\s*([-=_]{30,}+)\\s*", " ");
// URLs in the preview should just be shown as "..." - They're not
// clickable and they usually overwhelm the preview
text = text.replaceAll("https?://\\S+", "...");
// Don't show newlines in the preview
text = text.replaceAll("(\\r|\\n)+", " ");
// Collapse whitespace in the preview
text = text.replaceAll("\\s+", " ");
// Remove any whitespace at the beginning and end of the string.
text = text.trim();
return (text.length() <= 512) ? text : text.substring(0, 512);
}
2010-11-30 22:07:28 -05:00
public void delete(String trashFolderName) throws MessagingException {}
/*
* TODO Refactor Flags at some point to be able to store user defined flags.
*/
public Set<Flag> getFlags() {
return Collections.unmodifiableSet(mFlags);
}
2010-09-12 02:11:08 -04:00
/**
* @param flag
* Flag to set. Never <code>null</code>.
* @param set
* If <code>true</code>, the flag is added. If <code>false</code>
* , the flag is removed.
* @throws MessagingException
*/
public void setFlag(Flag flag, boolean set) throws MessagingException {
if (set) {
mFlags.add(flag);
} else {
mFlags.remove(flag);
}
}
/**
* This method calls setFlag(Flag, boolean)
* @param flags
* @param set
*/
public void setFlags(final Set<Flag> flags, boolean set) throws MessagingException {
for (Flag flag : flags) {
setFlag(flag, set);
}
}
public boolean isSet(Flag flag) {
return mFlags.contains(flag);
}
public void destroy() throws MessagingException {}
@Override
public abstract void setEncoding(String encoding) throws MessagingException;
2010-05-11 22:51:59 -04:00
public abstract void setCharset(String charset) throws MessagingException;
public long calculateSize() {
try {
CountingOutputStream out = new CountingOutputStream();
EOLConvertingOutputStream eolOut = new EOLConvertingOutputStream(out);
writeTo(eolOut);
eolOut.flush();
return out.getCount();
} catch (IOException e) {
2014-12-16 06:51:52 -05:00
Log.e(LOG_TAG, "Failed to calculate a message size", e);
} catch (MessagingException e) {
2014-12-16 06:51:52 -05:00
Log.e(LOG_TAG, "Failed to calculate a message size", e);
}
return 0;
}
/**
* Copy the contents of this object into another {@code Message} object.
*
2014-12-12 08:04:59 -05:00
* @param destination The {@code Message} object to receive the contents of this instance.
*/
protected void copy(Message destination) {
destination.mUid = mUid;
destination.mInternalDate = mInternalDate;
destination.mFolder = mFolder;
// mFlags contents can change during the object lifetime, so copy the Set
2014-10-05 07:08:55 -04:00
destination.mFlags = EnumSet.copyOf(mFlags);
}
/**
* Creates a new {@code Message} object with the same content as this object.
*
* <p>
* <strong>Note:</strong>
* This method was introduced as a hack to prevent {@code ConcurrentModificationException}s. It
* shouldn't be used unless absolutely necessary. See the comment in
* {@link com.fsck.k9.activity.MessageView.Listener#loadMessageForViewHeadersAvailable(com.fsck.k9.Account, String, String, Message)}
* for more information.
* </p>
*/
@Override
public abstract Message clone();
}