1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-23 09:52:16 -05:00

MimeUtility / Message refactor

* break MimeUtility class into manageable pieces (MessageExtractor/CharsetSupport)
 * move HTML related code out of the mail package
This commit is contained in:
Jan Berkel 2014-12-15 12:05:21 +01:00
parent 476cb1d4ce
commit 7d6e6b8abe
34 changed files with 2549 additions and 2513 deletions

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.internet;
package com.fsck.k9.activity;
import java.io.Serializable;
@ -12,7 +12,7 @@ import java.io.Serializable;
*
* TODO: This container should also have a text part, along with its insertion point. Or maybe a generic InsertableContent and maintain one each for Html and Text?
*/
public class InsertableHtmlContent implements Serializable {
class InsertableHtmlContent implements Serializable {
private static final long serialVersionUID = 2397327034L;
// Default to a headerInsertionPoint at the beginning of the message.
private int headerInsertionPoint = 0;

View File

@ -90,13 +90,12 @@ import com.fsck.k9.fragment.ProgressDialogFragment;
import com.fsck.k9.helper.ContactItem;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.internet.HtmlConverter;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.IdentityHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.internet.InsertableHtmlContent;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
@ -108,7 +107,6 @@ import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.internet.TextBodyBuilder;
import com.fsck.k9.mailstore.LocalAttachmentBody;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.TempFileBody;
@ -2957,10 +2955,10 @@ public class MessageCompose extends K9Activity implements OnClickListener,
if (messageFormat == MessageFormat.HTML) {
Part part = MimeUtility.findFirstPartByMimeType(message, "text/html");
Part part = message.findFirstPartByMimeType("text/html");
if (part != null) { // Shouldn't happen if we were the one who saved it.
mQuotedTextFormat = SimpleMessageFormat.HTML;
String text = MimeUtility.getTextFromPart(part);
String text = part.getText();
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
@ -3023,9 +3021,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
*/
private void processSourceMessageText(Message message, Integer bodyOffset, Integer bodyLength,
boolean viewMessageContent) throws MessagingException {
Part textPart = MimeUtility.findFirstPartByMimeType(message, "text/plain");
Part textPart = message.findFirstPartByMimeType("text/plain");
if (textPart != null) {
String text = MimeUtility.getTextFromPart(textPart);
String text = textPart.getText();
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
@ -3093,7 +3091,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
// Figure out which message format to use for the quoted text by looking if the source
// message contains a text/html part. If it does, we use that.
mQuotedTextFormat =
(MimeUtility.findFirstPartByMimeType(mSourceMessage, "text/html") == null) ?
(mSourceMessage.findFirstPartByMimeType("text/html") == null) ?
SimpleMessageFormat.TEXT : SimpleMessageFormat.HTML;
} else {
mQuotedTextFormat = SimpleMessageFormat.HTML;
@ -3223,37 +3221,37 @@ public class MessageCompose extends K9Activity implements OnClickListener,
Part part;
if (format == SimpleMessageFormat.HTML) {
// HTML takes precedence, then text.
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
part = message.findFirstPartByMimeType("text/html");
if (part != null) {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, HTML found.");
}
return MimeUtility.getTextFromPart(part);
return part.getText();
}
part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
part = message.findFirstPartByMimeType("text/plain");
if (part != null) {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, text found.");
}
return HtmlConverter.textToHtml(MimeUtility.getTextFromPart(part));
return HtmlConverter.textToHtml(part.getText());
}
} else if (format == SimpleMessageFormat.TEXT) {
// Text takes precedence, then html.
part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
part = message.findFirstPartByMimeType("text/plain");
if (part != null) {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, text found.");
}
return MimeUtility.getTextFromPart(part);
return part.getText();
}
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
part = message.findFirstPartByMimeType("text/html");
if (part != null) {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, HTML found.");
}
return HtmlConverter.htmlToText(MimeUtility.getTextFromPart(part));
return HtmlConverter.htmlToText(part.getText());
}
}

View File

@ -1,12 +1,14 @@
package com.fsck.k9.mail.internet;
package com.fsck.k9.activity;
import android.text.TextUtils;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.mail.Body;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.mail.internet.TextBody;
public class TextBodyBuilder {
class TextBodyBuilder {
private boolean mIncludeQuotedText = true;
private boolean mReplyAfterQuote = false;
private boolean mSignatureBeforeQuotedText = false;

View File

@ -12,7 +12,7 @@ import android.widget.TextView;
import com.fsck.k9.R;
import com.fsck.k9.activity.Accounts;
import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.mail.internet.HtmlConverter;
import com.fsck.k9.helper.HtmlConverter;
/**
* Displays a welcome message when no accounts have been created yet.

View File

@ -1744,7 +1744,7 @@ public class MessagingController implements Runnable {
* right now, attachments will be left for later.
*/
Set<Part> viewables = MimeUtility.collectTextParts(message);
Set<Part> viewables = message.collectTextParts();
/*
* Now download the parts we're interested in storing.
@ -3197,7 +3197,7 @@ public class MessagingController implements Runnable {
try {
LocalStore localStore = account.getLocalStore();
List<Part> attachments = MimeUtility.collectAttachments(message);
List<Part> attachments = message.collectAttachments();
for (Part attachment : attachments) {
attachment.setBody(null);
}
@ -4244,13 +4244,12 @@ public class MessagingController implements Runnable {
try {
Intent msg = new Intent(Intent.ACTION_SEND);
String quotedText = null;
Part part = MimeUtility.findFirstPartByMimeType(message,
"text/plain");
Part part = message.findFirstPartByMimeType("text/plain");
if (part == null) {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
part = message.findFirstPartByMimeType("text/html");
}
if (part != null) {
quotedText = MimeUtility.getTextFromPart(part);
quotedText = part.getText();
}
if (quotedText != null) {
msg.putExtra(Intent.EXTRA_TEXT, quotedText);

View File

@ -32,12 +32,12 @@ public class CryptoHelper {
public boolean isEncrypted(Message message) {
String data = null;
try {
Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
Part part = message.findFirstPartByMimeType("text/plain");
if (part == null) {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
part = message.findFirstPartByMimeType("text/html");
}
if (part != null) {
data = MimeUtility.getTextFromPart(part);
data = part.getText();
}
} catch (MessagingException e) {
// guess not...
@ -55,12 +55,12 @@ public class CryptoHelper {
public boolean isSigned(Message message) {
String data = null;
try {
Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
Part part = message.findFirstPartByMimeType("text/plain");
if (part == null) {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
part = message.findFirstPartByMimeType("text/html");
}
if (part != null) {
data = MimeUtility.getTextFromPart(part);
data = part.getText();
}
} catch (MessagingException e) {
// guess not...

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.internet;
package com.fsck.k9.helper;
import android.text.*;
import android.text.Html.TagHandler;

View File

@ -1,6 +1,9 @@
package com.fsck.k9.mail;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeUtility;
public abstract class BodyPart implements Part {
private Multipart mParent;
@ -15,5 +18,23 @@ public abstract class BodyPart implements Part {
public abstract void setEncoding(String encoding) throws MessagingException;
@Override
public abstract void setUsing7bitTransport() throws MessagingException;
public String getContentDisposition() {
try {
String disposition = getDisposition();
if (disposition != null) {
return MimeUtility.getHeaderParameter(disposition, null);
}
} catch (MessagingException e) { /* ignore */ }
return null;
}
@Override
public String getText() {
return MessageExtractor.getTextFromPart(this);
}
@Override
public Part findFirstPartByMimeType(String mimeType) throws MessagingException {
return MimeUtility.findFirstPartByMimeType(this, mimeType);
}
}

View File

@ -2,9 +2,11 @@
package com.fsck.k9.mail;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import android.util.Log;
@ -12,6 +14,8 @@ import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.mail.filter.CountingOutputStream;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeUtility;
public abstract class Message implements Part, CompositeBody {
@ -265,4 +269,69 @@ public abstract class Message implements Part, CompositeBody {
*/
@Override
public abstract Message clone();
/**
* Get the value of the {@code Content-Disposition} header.
* @return The value of the {@code Content-Disposition} header if available. {@code null}, otherwise.
*/
public String getContentDisposition() {
try {
String disposition = getDisposition();
if (disposition != null) {
return MimeUtility.getHeaderParameter(disposition, null);
}
}
catch (MessagingException e) { /* ignore */ }
return null;
}
@Override
public String getText() {
return MessageExtractor.getTextFromPart(this);
}
@Override
public Part findFirstPartByMimeType(String mimeType) throws MessagingException {
return MimeUtility.findFirstPartByMimeType(this, mimeType);
}
/**
* Collect attachment parts of a message.
*
* @param message
* The message to collect the attachment parts from.
*
* @return A list of parts regarded as attachments.
*
* @throws MessagingException
* In case of an error.
*/
public List<Part> collectAttachments() throws MessagingException {
try {
List<Part> attachments = new ArrayList<Part>();
MessageExtractor.getViewables(this, attachments);
return attachments;
} catch (Exception e) {
throw new MessagingException("Couldn't collect attachment parts", e);
}
}
/**
* Collect the viewable textual parts of a message.
*
* @param message
* The message to extract the viewable parts from.
*
* @return A set of viewable parts of the message.
*
* @throws MessagingException
* In case of an error.
*/
public Set<Part> collectTextParts() throws MessagingException {
try {
return MessageExtractor.getTextParts(this);
} catch (Exception e) {
throw new MessagingException("Couldn't extract viewable parts", e);
}
}
}

View File

@ -7,7 +7,7 @@ import java.util.List;
import org.apache.james.mime4j.util.MimeUtil;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.CharsetSupport;
import com.fsck.k9.mail.internet.TextBody;
public abstract class Multipart implements CompositeBody {
@ -64,7 +64,7 @@ public abstract class Multipart implements CompositeBody {
BodyPart part = mParts.get(0);
Body body = part.getBody();
if (body instanceof TextBody) {
MimeUtility.setCharset(charset, part);
CharsetSupport.setCharset(charset, part);
((TextBody)body).setCharset(charset);
}
}

View File

@ -5,29 +5,41 @@ import java.io.IOException;
import java.io.OutputStream;
public interface Part {
public void addHeader(String name, String value) throws MessagingException;
void addHeader(String name, String value) throws MessagingException;
public void removeHeader(String name) throws MessagingException;
void removeHeader(String name) throws MessagingException;
public void setHeader(String name, String value) throws MessagingException;
void setHeader(String name, String value) throws MessagingException;
public Body getBody();
Body getBody();
public String getContentType() throws MessagingException;
String getContentType() throws MessagingException;
public String getDisposition() throws MessagingException;
String getDisposition() throws MessagingException;
public String getContentId() throws MessagingException;
String getContentDisposition();
public String[] getHeader(String name) throws MessagingException;
String getContentId() throws MessagingException;
public boolean isMimeType(String mimeType) throws MessagingException;
String[] getHeader(String name) throws MessagingException;
public String getMimeType() throws MessagingException;
boolean isMimeType(String mimeType) throws MessagingException;
public void setBody(Body body) throws MessagingException;
String getMimeType() throws MessagingException;
public void writeTo(OutputStream out) throws IOException, MessagingException;
void setBody(Body body) throws MessagingException;
void writeTo(OutputStream out) throws IOException, MessagingException;
/**
* Reads the Part's body and returns a String based on any charset conversion that needed
* to be done. Note, this <b>does not</b> return a text representation of HTML.
* @return a String containing the converted text in the body, or null if there was no text
* or an error during conversion.
*/
String getText();
Part findFirstPartByMimeType(String mimeType) throws MessagingException;
/**
* Called just prior to transmission, once the type of transport is known to
@ -41,5 +53,5 @@ public interface Part {
*
*/
//TODO perhaps it would be clearer to use a flag "force7bit" in writeTo
public abstract void setUsing7bitTransport() throws MessagingException;
void setUsing7bitTransport() throws MessagingException;
}

File diff suppressed because it is too large Load Diff

View File

@ -35,7 +35,7 @@ class DecoderUtil {
Base64InputStream is = new Base64InputStream(new ByteArrayInputStream(bytes));
try {
return MimeUtility.readToString(is, charset);
return CharsetSupport.readToString(is, charset);
} catch (IOException e) {
return null;
}
@ -68,7 +68,7 @@ class DecoderUtil {
QuotedPrintableInputStream is = new QuotedPrintableInputStream(new ByteArrayInputStream(bytes));
try {
return MimeUtility.readToString(is, charset);
return CharsetSupport.readToString(is, charset);
} catch (IOException e) {
return null;
}
@ -162,7 +162,7 @@ class DecoderUtil {
String charset;
try {
charset = MimeUtility.fixupCharset(mimeCharset, message);
charset = CharsetSupport.fixupCharset(mimeCharset, message);
} catch (MessagingException e) {
return null;
}

View File

@ -68,7 +68,7 @@ class EncoderUtil {
if (charset == null)
charset = determineCharset(text);
String mimeCharset = MimeUtility.getExternalCharset(charset.name());
String mimeCharset = CharsetSupport.getExternalCharset(charset.name());
byte[] bytes = encode(text, charset);

View File

@ -0,0 +1,103 @@
package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
class JisSupport {
public static final String SHIFT_JIS = "shift_jis";
public static String getJisVariantFromMessage(Message message) throws MessagingException {
if (message == null)
return null;
// If a receiver is known to use a JIS variant, the sender transfers the message after converting the
// charset as a convention.
String variant = getJisVariantFromReceivedHeaders(message);
if (variant != null)
return variant;
// If a receiver is not known to use any JIS variants, the sender transfers the message without converting
// the charset.
variant = getJisVariantFromFromHeaders(message);
if (variant != null)
return variant;
return getJisVariantFromMailerHeaders(message);
}
public static boolean isShiftJis(String charset) {
return charset.length() > 17 && charset.startsWith("x-")
&& charset.endsWith("-shift_jis-2007");
}
public static String getJisVariantFromAddress(String address) {
if (address == null)
return null;
if (isInDomain(address, "docomo.ne.jp") || isInDomain(address, "dwmail.jp") ||
isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com") ||
isInDomain(address, "emnet.ne.jp") || isInDomain(address, "emobile.ne.jp"))
return "docomo";
else if (isInDomain(address, "softbank.ne.jp") || isInDomain(address, "vodafone.ne.jp") ||
isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp"))
return "softbank";
else if (isInDomain(address, "ezweb.ne.jp") || isInDomain(address, "ido.ne.jp"))
return "kddi";
return null;
}
private static String getJisVariantFromMailerHeaders(Message message) throws MessagingException {
String mailerHeaders[] = message.getHeader("X-Mailer");
if (mailerHeaders == null || mailerHeaders.length == 0)
return null;
if (mailerHeaders[0].startsWith("iPhone Mail ") || mailerHeaders[0].startsWith("iPad Mail "))
return "iphone";
return null;
}
private static String getJisVariantFromReceivedHeaders(Part message) throws MessagingException {
String receivedHeaders[] = message.getHeader("Received");
if (receivedHeaders == null)
return null;
for (String receivedHeader : receivedHeaders) {
String address = getAddressFromReceivedHeader(receivedHeader);
if (address == null)
continue;
String variant = getJisVariantFromAddress(address);
if (variant != null)
return variant;
}
return null;
}
private static String getAddressFromReceivedHeader(String receivedHeader) {
// Not implemented yet! Extract an address from the FOR clause of the given Received header.
return null;
}
private static String getJisVariantFromFromHeaders(Message message) throws MessagingException {
Address addresses[] = message.getFrom();
if (addresses == null || addresses.length == 0)
return null;
return getJisVariantFromAddress(addresses[0].getAddress());
}
private static boolean isInDomain(String address, String domain) {
int index = address.length() - domain.length() - 1;
if (index < 0)
return false;
char c = address.charAt(index);
if (c != '@' && c != '.')
return false;
return address.endsWith(domain);
}
}

View File

@ -0,0 +1,410 @@
package com.fsck.k9.mail.internet;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.fsck.k9.mail.internet.CharsetSupport.fixupCharset;
import static com.fsck.k9.mail.internet.MimeUtility.getHeaderParameter;
import static com.fsck.k9.mail.internet.Viewable.Alternative;
import static com.fsck.k9.mail.internet.Viewable.Textual;
public class MessageExtractor {
public static String getTextFromPart(Part part) {
try {
if ((part != null) && (part.getBody() != null)) {
final Body body = part.getBody();
if (body instanceof TextBody) {
return ((TextBody)body).getText();
}
final String mimeType = part.getMimeType();
if ((mimeType != null) && MimeUtility.mimeTypeMatches(mimeType, "text/*")) {
/*
* We've got a text part, so let's see if it needs to be processed further.
*/
String charset = getHeaderParameter(part.getContentType(), "charset");
/*
* determine the charset from HTML message.
*/
if (mimeType.equalsIgnoreCase("text/html") && charset == null) {
InputStream in = part.getBody().getInputStream();
try {
byte[] buf = new byte[256];
in.read(buf, 0, buf.length);
String str = new String(buf, "US-ASCII");
if (str.isEmpty()) {
return "";
}
Pattern p = Pattern.compile("<meta http-equiv=\"?Content-Type\"? content=\"text/html; charset=(.+?)\">", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(str);
if (m.find()) {
charset = m.group(1);
}
} finally {
try {
if (in instanceof BinaryTempFileBody.BinaryTempFileBodyInputStream) {
/*
* If this is a BinaryTempFileBodyInputStream, calling close()
* will delete the file. But we can't let that happen because
* the file needs to be opened again by the code a few lines
* down.
*/
((BinaryTempFileBody.BinaryTempFileBodyInputStream) in).closeWithoutDeleting();
} else {
in.close();
}
} catch (Exception e) { /* ignore */ }
}
}
charset = fixupCharset(charset, getMessageFromPart(part));
/*
* Now we read the part into a buffer for further processing. Because
* the stream is now wrapped we'll remove any transfer encoding at this point.
*/
InputStream in = part.getBody().getInputStream();
try {
String text = CharsetSupport.readToString(in, charset);
// Replace the body with a TextBody that already contains the decoded text
part.setBody(new TextBody(text));
return text;
} finally {
try {
/*
* This time we don't care if it's a BinaryTempFileBodyInputStream. We
* replaced the body with a TextBody instance and hence don't need the
* file anymore.
*/
in.close();
} catch (IOException e) { /* Ignore */ }
}
}
}
} catch (OutOfMemoryError oom) {
/*
* If we are not able to process the body there's nothing we can do about it. Return
* null and let the upper layers handle the missing content.
*/
Log.e(K9.LOG_TAG, "Unable to getTextFromPart " + oom.toString());
} catch (Exception e) {
/*
* If we are not able to process the body there's nothing we can do about it. Return
* null and let the upper layers handle the missing content.
*/
Log.e(K9.LOG_TAG, "Unable to getTextFromPart", e);
}
return null;
}
/**
* Traverse the MIME tree of a message an extract viewable parts.
*
* @param part
* The message part to start from.
* @param attachments
* A list that will receive the parts that are considered attachments.
*
* @return A list of {@link Viewable}s.
*
* @throws MessagingException
* In case of an error.
*/
public static List<Viewable> getViewables(Part part, List<Part> attachments) throws MessagingException {
List<Viewable> viewables = new ArrayList<Viewable>();
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart multipart = (Multipart) body;
if (part.getMimeType().equalsIgnoreCase("multipart/alternative")) {
/*
* For multipart/alternative parts we try to find a text/plain and a text/html
* child. Everything else we find is put into 'attachments'.
*/
List<Viewable> text = findTextPart(multipart, true);
Set<Part> knownTextParts = getParts(text);
List<Viewable> html = findHtmlPart(multipart, knownTextParts, attachments, true);
if (!text.isEmpty() || !html.isEmpty()) {
Alternative alternative = new Alternative(text, html);
viewables.add(alternative);
}
} else {
// For all other multipart parts we recurse to grab all viewable children.
for (Part bodyPart : multipart.getBodyParts()) {
viewables.addAll(getViewables(bodyPart, attachments));
}
}
} else if (body instanceof Message &&
!("attachment".equalsIgnoreCase(part.getContentDisposition()))) {
/*
* We only care about message/rfc822 parts whose Content-Disposition header has a value
* other than "attachment".
*/
Message message = (Message) body;
// We add the Message object so we can extract the filename later.
viewables.add(new Viewable.MessageHeader(part, message));
// Recurse to grab all viewable parts and attachments from that message.
viewables.addAll(getViewables(message, attachments));
} else if (isPartTextualBody(part)) {
/*
* Save text/plain and text/html
*/
String mimeType = part.getMimeType();
if (mimeType.equalsIgnoreCase("text/plain")) {
Viewable.Text text = new Viewable.Text(part);
viewables.add(text);
} else {
Viewable.Html html = new Viewable.Html(part);
viewables.add(html);
}
} else {
// Everything else is treated as attachment.
attachments.add(part);
}
return viewables;
}
public static Set<Part> getTextParts(Part part) throws MessagingException {
List<Part> attachments = new ArrayList<Part>();
return getParts(getViewables(part, attachments));
}
private static Message getMessageFromPart(Part part) {
while (part != null) {
if (part instanceof Message)
return (Message)part;
if (!(part instanceof BodyPart))
return null;
Multipart multipart = ((BodyPart)part).getParent();
if (multipart == null)
return null;
part = multipart.getParent();
}
return null;
}
/**
* Search the children of a {@link Multipart} for {@code text/plain} parts.
*
* @param multipart The {@code Multipart} to search through.
* @param directChild If {@code true}, this method will return after the first {@code text/plain} was
* found.
*
* @return A list of {@link Viewable.Text} viewables.
*
* @throws MessagingException
* In case of an error.
*/
private static List<Viewable> findTextPart(Multipart multipart, boolean directChild)
throws MessagingException {
List<Viewable> viewables = new ArrayList<Viewable>();
for (Part part : multipart.getBodyParts()) {
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
/*
* Recurse to find text parts. Since this is a multipart that is a child of a
* multipart/alternative we don't want to stop after the first text/plain part
* we find. This will allow to get all text parts for constructions like this:
*
* 1. multipart/alternative
* 1.1. multipart/mixed
* 1.1.1. text/plain
* 1.1.2. text/plain
* 1.2. text/html
*/
List<Viewable> textViewables = findTextPart(innerMultipart, false);
if (!textViewables.isEmpty()) {
viewables.addAll(textViewables);
if (directChild) {
break;
}
}
} else if (isPartTextualBody(part) && part.getMimeType().equalsIgnoreCase("text/plain")) {
Viewable.Text text = new Viewable.Text(part);
viewables.add(text);
if (directChild) {
break;
}
}
}
return viewables;
}
/**
* Search the children of a {@link Multipart} for {@code text/html} parts.
* Every part that is not a {@code text/html} we want to display, we add to 'attachments'.
*
* @param multipart The {@code Multipart} to search through.
* @param knownTextParts A set of {@code text/plain} parts that shouldn't be added to 'attachments'.
* @param attachments A list that will receive the parts that are considered attachments.
* @param directChild If {@code true}, this method will add all {@code text/html} parts except the first
* found to 'attachments'.
*
* @return A list of {@link Viewable.Text} viewables.
*
* @throws MessagingException In case of an error.
*/
private static List<Viewable> findHtmlPart(Multipart multipart, Set<Part> knownTextParts,
List<Part> attachments, boolean directChild) throws MessagingException {
List<Viewable> viewables = new ArrayList<Viewable>();
boolean partFound = false;
for (Part part : multipart.getBodyParts()) {
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
if (directChild && partFound) {
// We already found our text/html part. Now we're only looking for attachments.
findAttachments(innerMultipart, knownTextParts, attachments);
} else {
/*
* Recurse to find HTML parts. Since this is a multipart that is a child of a
* multipart/alternative we don't want to stop after the first text/html part
* we find. This will allow to get all text parts for constructions like this:
*
* 1. multipart/alternative
* 1.1. text/plain
* 1.2. multipart/mixed
* 1.2.1. text/html
* 1.2.2. text/html
* 1.3. image/jpeg
*/
List<Viewable> htmlViewables = findHtmlPart(innerMultipart, knownTextParts,
attachments, false);
if (!htmlViewables.isEmpty()) {
partFound = true;
viewables.addAll(htmlViewables);
}
}
} else if (!(directChild && partFound) && isPartTextualBody(part) &&
part.getMimeType().equalsIgnoreCase("text/html")) {
Viewable.Html html = new Viewable.Html(part);
viewables.add(html);
partFound = true;
} else if (!knownTextParts.contains(part)) {
// Only add this part as attachment if it's not a viewable text/plain part found
// earlier.
attachments.add(part);
}
}
return viewables;
}
/**
* Traverse the MIME tree and add everything that's not a known text part to 'attachments'.
*
* @param multipart
* The {@link Multipart} to start from.
* @param knownTextParts
* A set of known text parts we don't want to end up in 'attachments'.
* @param attachments
* A list that will receive the parts that are considered attachments.
*/
private static void findAttachments(Multipart multipart, Set<Part> knownTextParts,
List<Part> attachments) {
for (Part part : multipart.getBodyParts()) {
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
findAttachments(innerMultipart, knownTextParts, attachments);
} else if (!knownTextParts.contains(part)) {
attachments.add(part);
}
}
}
/**
* Build a set of message parts for fast lookups.
*
* @param viewables
* A list of {@link Viewable}s containing references to the message parts to include in
* the set.
*
* @return The set of viewable {@code Part}s.
*
* @see MimeUtility#findHtmlPart(Multipart, Set, List, boolean)
* @see MimeUtility#findAttachments(Multipart, Set, List)
*/
private static Set<Part> getParts(List<Viewable> viewables) {
Set<Part> parts = new HashSet<Part>();
for (Viewable viewable : viewables) {
if (viewable instanceof Textual) {
parts.add(((Textual) viewable).getPart());
} else if (viewable instanceof Alternative) {
Alternative alternative = (Alternative) viewable;
parts.addAll(getParts(alternative.getText()));
parts.addAll(getParts(alternative.getHtml()));
}
}
return parts;
}
private static Boolean isPartTextualBody(Part part) throws MessagingException {
String disposition = part.getDisposition();
String dispositionType = null;
String dispositionFilename = null;
if (disposition != null) {
dispositionType = MimeUtility.getHeaderParameter(disposition, null);
dispositionFilename = MimeUtility.getHeaderParameter(disposition, "filename");
}
/*
* A best guess that this part is intended to be an attachment and not inline.
*/
boolean attachment = ("attachment".equalsIgnoreCase(dispositionType) || (dispositionFilename != null));
if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/html"))) {
return true;
}
/*
* If the part is plain text and it got this far it's part of a
* mixed (et al) and should be rendered inline.
*/
else if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/plain"))) {
return true;
}
/*
* Finally, if it's nothing else we will include it as an attachment.
*/
else {
return false;
}
}
}

View File

@ -473,7 +473,7 @@ public class MimeMessage extends Message {
if (mBody instanceof Multipart) {
((Multipart)mBody).setCharset(charset);
} else if (mBody instanceof TextBody) {
MimeUtility.setCharset(charset, this);
CharsetSupport.setCharset(charset, this);
((TextBody)mBody).setCharset(charset);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,11 @@ package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
import org.apache.james.mime4j.util.MimeUtil;

View File

@ -0,0 +1,105 @@
package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Part;
import java.util.List;
/**
* Empty marker class interface the class hierarchy used by
* {@link com.fsck.k9.mailstore.LocalMessageExtractor#extractTextAndAttachments(android.content.Context, com.fsck.k9.mail.Message)}.
*
* @see Viewable.Text
* @see Viewable.Html
* @see Viewable.MessageHeader
* @see Viewable.Alternative
*/
public interface Viewable {
/**
* Class representing textual parts of a message that aren't marked as attachments.
*
* @see com.fsck.k9.mail.internet.MessageExtractor#isPartTextualBody(com.fsck.k9.mail.Part)
*/
static abstract class Textual implements Viewable {
private Part mPart;
public Textual(Part part) {
mPart = part;
}
public Part getPart() {
return mPart;
}
}
/**
* Class representing a {@code text/plain} part of a message.
*/
static class Text extends Textual {
public Text(Part part) {
super(part);
}
}
/**
* Class representing a {@code text/html} part of a message.
*/
static class Html extends Textual {
public Html(Part part) {
super(part);
}
}
/**
* Class representing a {@code message/rfc822} part of a message.
*
* <p>
* This is used to extract basic header information when the message contents are displayed
* inline.
* </p>
*/
static class MessageHeader implements Viewable {
private Part mContainerPart;
private Message mMessage;
public MessageHeader(Part containerPart, Message message) {
mContainerPart = containerPart;
mMessage = message;
}
public Part getContainerPart() {
return mContainerPart;
}
public Message getMessage() {
return mMessage;
}
}
/**
* Class representing a {@code multipart/alternative} part of a message.
*
* <p>
* Only relevant {@code text/plain} and {@code text/html} children are stored in this container
* class.
* </p>
*/
static class Alternative implements Viewable {
private List<Viewable> mText;
private List<Viewable> mHtml;
public Alternative(List<Viewable> text, List<Viewable> html) {
mText = text;
mHtml = html;
}
public List<Viewable> getText() {
return mText;
}
public List<Viewable> getHtml() {
return mHtml;
}
}
}

View File

@ -12,7 +12,7 @@ import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.filter.LineWrapOutputStream;
import com.fsck.k9.mail.filter.PeekableInputStream;
import com.fsck.k9.mail.filter.SmtpDataStuffing;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.CharsetSupport;
import com.fsck.k9.mail.store.StoreConfig;
import com.fsck.k9.mail.ssl.TrustedSocketFactory;
@ -463,7 +463,7 @@ public class SmtpTransport extends Transport {
new HashMap<String, List<String>>();
for (Address address : addresses) {
String addressString = address.getAddress();
String charset = MimeUtility.getCharsetFromAddress(addressString);
String charset = CharsetSupport.getCharsetFromAddress(addressString);
List<String> addressesOfCharset = charsetAddressesMap.get(charset);
if (addressesOfCharset == null) {
addressesOfCharset = new ArrayList<String>();

View File

@ -33,7 +33,7 @@ import com.fsck.k9.K9;
import com.fsck.k9.Account.MessageFormat;
import com.fsck.k9.activity.Search;
import com.fsck.k9.mail.MessageRetrievalListener;
import com.fsck.k9.mail.internet.HtmlConverter;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
@ -51,11 +51,11 @@ import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.internet.MimeUtility.ViewableContainer;
import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
import com.fsck.k9.mailstore.LockableDatabase.WrappedException;
import com.fsck.k9.provider.AttachmentProvider;
public class LocalFolder extends Folder<LocalMessage> implements Serializable {
private static final long serialVersionUID = -1973296520918624767L;
@ -1299,14 +1299,14 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
// draft messages because this will cause the values stored in
// the identity header to be wrong.
ViewableContainer container =
MimeUtility.extractPartsFromDraft(message);
LocalMessageExtractor.extractPartsFromDraft(message);
text = container.text;
html = container.html;
attachments = container.attachments;
} else {
ViewableContainer container =
MimeUtility.extractTextAndAttachments(LocalFolder.this.localStore.mApplication, message);
LocalMessageExtractor.extractTextAndAttachments(LocalFolder.this.localStore.mApplication, message);
attachments = container.attachments;
text = container.text;
@ -1412,7 +1412,7 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
message.buildMimeRepresentation();
ViewableContainer container =
MimeUtility.extractTextAndAttachments(LocalFolder.this.localStore.mApplication, message);
LocalMessageExtractor.extractTextAndAttachments(LocalFolder.this.localStore.mApplication, message);
List<Part> attachments = container.attachments;
String text = container.text;

View File

@ -120,16 +120,16 @@ public class LocalMessage extends MimeMessage {
*/
public String getTextForDisplay() throws MessagingException {
String text = null; // First try and fetch an HTML part.
Part part = MimeUtility.findFirstPartByMimeType(this, "text/html");
Part part = findFirstPartByMimeType("text/html");
if (part == null) {
// If that fails, try and get a text part.
part = MimeUtility.findFirstPartByMimeType(this, "text/plain");
part = findFirstPartByMimeType("text/plain");
if (part != null && part.getBody() instanceof LocalTextBody) {
text = ((LocalTextBody) part.getBody()).getBodyForDisplay();
}
} else {
// We successfully found an HTML part; do the necessary character set decoding.
text = MimeUtility.getTextFromPart(part);
text = part.getText();
}
return text;
}

View File

@ -0,0 +1,464 @@
package com.fsck.k9.mailstore;
import android.content.Context;
import com.fsck.k9.R;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.Viewable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static com.fsck.k9.mail.internet.MimeUtility.getHeaderParameter;
class LocalMessageExtractor {
private static final String TEXT_DIVIDER =
"------------------------------------------------------------------------";
private static final int TEXT_DIVIDER_LENGTH = TEXT_DIVIDER.length();
private static final String FILENAME_PREFIX = "----- ";
private static final int FILENAME_PREFIX_LENGTH = FILENAME_PREFIX.length();
private static final String FILENAME_SUFFIX = " ";
private static final int FILENAME_SUFFIX_LENGTH = FILENAME_SUFFIX.length();
/**
* Extract the viewable textual parts of a message and return the rest as attachments.
*
* @param context A {@link android.content.Context} instance that will be used to get localized strings.
* @return A {@link ViewableContainer} instance containing the textual parts of the message as
* plain text and HTML, and a list of message parts considered attachments.
*
* @throws com.fsck.k9.mail.MessagingException
* In case of an error.
*/
public static ViewableContainer extractTextAndAttachments(Context context, Message message) throws MessagingException {
try {
List<Part> attachments = new ArrayList<Part>();
// Collect all viewable parts
List<Viewable> viewables = MessageExtractor.getViewables(message, attachments);
/*
* Convert the tree of viewable parts into text and HTML
*/
// Used to suppress the divider for the first viewable part
boolean hideDivider = true;
StringBuilder text = new StringBuilder();
StringBuilder html = new StringBuilder();
for (Viewable viewable : viewables) {
if (viewable instanceof Viewable.Textual) {
// This is either a text/plain or text/html part. Fill the variables 'text' and
// 'html', converting between plain text and HTML as necessary.
text.append(buildText(viewable, !hideDivider));
html.append(buildHtml(viewable, !hideDivider));
hideDivider = false;
} else if (viewable instanceof Viewable.MessageHeader) {
Viewable.MessageHeader header = (Viewable.MessageHeader) viewable;
Part containerPart = header.getContainerPart();
Message innerMessage = header.getMessage();
addTextDivider(text, containerPart, !hideDivider);
addMessageHeaderText(context, text, innerMessage);
addHtmlDivider(html, containerPart, !hideDivider);
addMessageHeaderHtml(context, html, innerMessage);
hideDivider = true;
} else if (viewable instanceof Viewable.Alternative) {
// Handle multipart/alternative contents
Viewable.Alternative alternative = (Viewable.Alternative) viewable;
/*
* We made sure at least one of text/plain or text/html is present when
* creating the Alternative object. If one part is not present we convert the
* other one to make sure 'text' and 'html' always contain the same text.
*/
List<Viewable> textAlternative = alternative.getText().isEmpty() ?
alternative.getHtml() : alternative.getText();
List<Viewable> htmlAlternative = alternative.getHtml().isEmpty() ?
alternative.getText() : alternative.getHtml();
// Fill the 'text' variable
boolean divider = !hideDivider;
for (Viewable textViewable : textAlternative) {
text.append(buildText(textViewable, divider));
divider = true;
}
// Fill the 'html' variable
divider = !hideDivider;
for (Viewable htmlViewable : htmlAlternative) {
html.append(buildHtml(htmlViewable, divider));
divider = true;
}
hideDivider = false;
}
}
return new ViewableContainer(text.toString(), html.toString(), attachments);
} catch (Exception e) {
throw new MessagingException("Couldn't extract viewable parts", e);
}
}
public static ViewableContainer extractPartsFromDraft(Message message)
throws MessagingException {
Body body = message.getBody();
if (message.isMimeType("multipart/mixed") && body instanceof MimeMultipart) {
MimeMultipart multipart = (MimeMultipart) body;
ViewableContainer container;
int count = multipart.getCount();
if (count >= 1) {
// The first part is either a text/plain or a multipart/alternative
BodyPart firstPart = multipart.getBodyPart(0);
container = extractTextual(firstPart);
// The rest should be attachments
for (int i = 1; i < count; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
container.attachments.add(bodyPart);
}
} else {
container = new ViewableContainer("", "", new ArrayList<Part>());
}
return container;
}
return extractTextual(message);
}
/**
* Use the contents of a {@link com.fsck.k9.mail.internet.Viewable} to create the HTML to be displayed.
*
* <p>
* This will use {@link com.fsck.k9.helper.HtmlConverter#textToHtml(String)} to convert plain text parts
* to HTML if necessary.
* </p>
*
* @param viewable
* The viewable part to build the HTML from.
* @param prependDivider
* {@code true}, if the HTML divider should be inserted as first element.
* {@code false}, otherwise.
*
* @return The contents of the supplied viewable instance as HTML.
*/
private static StringBuilder buildHtml(Viewable viewable, boolean prependDivider)
{
StringBuilder html = new StringBuilder();
if (viewable instanceof Viewable.Textual) {
Part part = ((Viewable.Textual)viewable).getPart();
addHtmlDivider(html, part, prependDivider);
String t = part.getText();
if (t == null) {
t = "";
} else if (viewable instanceof Viewable.Text) {
t = HtmlConverter.textToHtml(t);
}
html.append(t);
} else if (viewable instanceof Viewable.Alternative) {
// That's odd - an Alternative as child of an Alternative; go ahead and try to use the
// text/html child; fall-back to the text/plain part.
Viewable.Alternative alternative = (Viewable.Alternative) viewable;
List<Viewable> htmlAlternative = alternative.getHtml().isEmpty() ?
alternative.getText() : alternative.getHtml();
boolean divider = prependDivider;
for (Viewable htmlViewable : htmlAlternative) {
html.append(buildHtml(htmlViewable, divider));
divider = true;
}
}
return html;
}
private static StringBuilder buildText(Viewable viewable, boolean prependDivider)
{
StringBuilder text = new StringBuilder();
if (viewable instanceof Viewable.Textual) {
Part part = ((Viewable.Textual)viewable).getPart();
addTextDivider(text, part, prependDivider);
String t = part.getText();
if (t == null) {
t = "";
} else if (viewable instanceof Viewable.Html) {
t = HtmlConverter.htmlToText(t);
}
text.append(t);
} else if (viewable instanceof Viewable.Alternative) {
// That's odd - an Alternative as child of an Alternative; go ahead and try to use the
// text/plain child; fall-back to the text/html part.
Viewable.Alternative alternative = (Viewable.Alternative) viewable;
List<Viewable> textAlternative = alternative.getText().isEmpty() ?
alternative.getHtml() : alternative.getText();
boolean divider = prependDivider;
for (Viewable textViewable : textAlternative) {
text.append(buildText(textViewable, divider));
divider = true;
}
}
return text;
}
/**
* Add an HTML divider between two HTML message parts.
*
* @param html
* The {@link StringBuilder} to append the divider to.
* @param part
* The message part that will follow after the divider. This is used to extract the
* part's name.
* @param prependDivider
* {@code true}, if the divider should be appended. {@code false}, otherwise.
*/
private static void addHtmlDivider(StringBuilder html, Part part, boolean prependDivider) {
if (prependDivider) {
String filename = getPartName(part);
html.append("<p style=\"margin-top: 2.5em; margin-bottom: 1em; border-bottom: 1px solid #000\">");
html.append(filename);
html.append("</p>");
}
}
/**
* Get the name of the message part.
*
* @param part
* The part to get the name for.
*
* @return The (file)name of the part if available. An empty string, otherwise.
*/
private static String getPartName(Part part) {
try {
String disposition = part.getDisposition();
if (disposition != null) {
String name = getHeaderParameter(disposition, "filename");
return (name == null) ? "" : name;
}
}
catch (MessagingException e) { /* ignore */ }
return "";
}
/**
* Add a plain text divider between two plain text message parts.
*
* @param text
* The {@link StringBuilder} to append the divider to.
* @param part
* The message part that will follow after the divider. This is used to extract the
* part's name.
* @param prependDivider
* {@code true}, if the divider should be appended. {@code false}, otherwise.
*/
private static void addTextDivider(StringBuilder text, Part part, boolean prependDivider) {
if (prependDivider) {
String filename = getPartName(part);
text.append("\r\n\r\n");
int len = filename.length();
if (len > 0) {
if (len > TEXT_DIVIDER_LENGTH - FILENAME_PREFIX_LENGTH - FILENAME_SUFFIX_LENGTH) {
filename = filename.substring(0, TEXT_DIVIDER_LENGTH - FILENAME_PREFIX_LENGTH -
FILENAME_SUFFIX_LENGTH - 3) + "...";
}
text.append(FILENAME_PREFIX);
text.append(filename);
text.append(FILENAME_SUFFIX);
text.append(TEXT_DIVIDER.substring(0, TEXT_DIVIDER_LENGTH -
FILENAME_PREFIX_LENGTH - filename.length() - FILENAME_SUFFIX_LENGTH));
} else {
text.append(TEXT_DIVIDER);
}
text.append("\r\n\r\n");
}
}
/**
* Extract important header values from a message to display inline (plain text version).
*
* @param context
* A {@link android.content.Context} instance that will be used to get localized strings.
* @param text
* The {@link StringBuilder} that will receive the (plain text) output.
* @param message
* The message to extract the header values from.
*
* @throws com.fsck.k9.mail.MessagingException
* In case of an error.
*/
private static void addMessageHeaderText(Context context, StringBuilder text, Message message)
throws MessagingException {
// From: <sender>
Address[] from = message.getFrom();
if (from != null && from.length > 0) {
text.append(context.getString(R.string.message_compose_quote_header_from));
text.append(' ');
text.append(Address.toString(from));
text.append("\r\n");
}
// To: <recipients>
Address[] to = message.getRecipients(Message.RecipientType.TO);
if (to != null && to.length > 0) {
text.append(context.getString(R.string.message_compose_quote_header_to));
text.append(' ');
text.append(Address.toString(to));
text.append("\r\n");
}
// Cc: <recipients>
Address[] cc = message.getRecipients(Message.RecipientType.CC);
if (cc != null && cc.length > 0) {
text.append(context.getString(R.string.message_compose_quote_header_cc));
text.append(' ');
text.append(Address.toString(cc));
text.append("\r\n");
}
// Date: <date>
Date date = message.getSentDate();
if (date != null) {
text.append(context.getString(R.string.message_compose_quote_header_send_date));
text.append(' ');
text.append(date.toString());
text.append("\r\n");
}
// Subject: <subject>
String subject = message.getSubject();
text.append(context.getString(R.string.message_compose_quote_header_subject));
text.append(' ');
if (subject == null) {
text.append(context.getString(R.string.general_no_subject));
} else {
text.append(subject);
}
text.append("\r\n\r\n");
}
/**
* Extract important header values from a message to display inline (HTML version).
*
* @param context
* A {@link android.content.Context} instance that will be used to get localized strings.
* @param html
* The {@link StringBuilder} that will receive the (HTML) output.
* @param message
* The message to extract the header values from.
*
* @throws com.fsck.k9.mail.MessagingException
* In case of an error.
*/
private static void addMessageHeaderHtml(Context context, StringBuilder html, Message message)
throws MessagingException {
html.append("<table style=\"border: 0\">");
// From: <sender>
Address[] from = message.getFrom();
if (from != null && from.length > 0) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_from),
Address.toString(from));
}
// To: <recipients>
Address[] to = message.getRecipients(Message.RecipientType.TO);
if (to != null && to.length > 0) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_to),
Address.toString(to));
}
// Cc: <recipients>
Address[] cc = message.getRecipients(Message.RecipientType.CC);
if (cc != null && cc.length > 0) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_cc),
Address.toString(cc));
}
// Date: <date>
Date date = message.getSentDate();
if (date != null) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_send_date),
date.toString());
}
// Subject: <subject>
String subject = message.getSubject();
addTableRow(html, context.getString(R.string.message_compose_quote_header_subject),
(subject == null) ? context.getString(R.string.general_no_subject) : subject);
html.append("</table>");
}
/**
* Output an HTML table two column row with some hardcoded style.
*
* @param html
* The {@link StringBuilder} that will receive the output.
* @param header
* The string to be put in the {@code TH} element.
* @param value
* The string to be put in the {@code TD} element.
*/
private static void addTableRow(StringBuilder html, String header, String value) {
html.append("<tr><th style=\"text-align: left; vertical-align: top;\">");
html.append(header);
html.append("</th>");
html.append("<td>");
html.append(value);
html.append("</td></tr>");
}
private static ViewableContainer extractTextual(Part part) throws MessagingException {
String text = "";
String html = "";
List<Part> attachments = new ArrayList<Part>();
Body firstBody = part.getBody();
if (part.isMimeType("text/plain")) {
String bodyText = part.getText();
if (bodyText != null) {
text = bodyText;
html = HtmlConverter.textToHtml(text);
}
} else if (part.isMimeType("multipart/alternative") &&
firstBody instanceof MimeMultipart) {
MimeMultipart multipart = (MimeMultipart) firstBody;
for (BodyPart bodyPart : multipart.getBodyParts()) {
String bodyText = bodyPart.getText();
if (bodyText != null) {
if (text.isEmpty() && bodyPart.isMimeType("text/plain")) {
text = bodyText;
} else if (html.isEmpty() && bodyPart.isMimeType("text/html")) {
html = bodyText;
}
}
}
}
return new ViewableContainer(text, html, attachments);
}
}

View File

@ -0,0 +1,34 @@
package com.fsck.k9.mailstore;
import com.fsck.k9.mail.Part;
import java.util.List;
/**
* Store viewable text of a message as plain text and HTML, and the parts considered
* attachments.
*
* @see LocalMessageExtractor#extractTextAndAttachments(android.content.Context, com.fsck.k9.mail.Message)
*/
class ViewableContainer {
/**
* The viewable text of the message in plain text.
*/
public final String text;
/**
* The viewable text of the message in HTML.
*/
public final String html;
/**
* The parts of the message considered attachments (everything not viewable).
*/
public final List<Part> attachments;
public ViewableContainer(String text, String html, List<Part> attachments) {
this.text = text;
this.html = html;
this.attachments = attachments;
}
}

View File

@ -215,8 +215,7 @@ public class MessageOpenPgpView extends LinearLayout {
} else {
try {
// check for PGP/MIME encryption
Part pgp = MimeUtility
.findFirstPartByMimeType(message, "application/pgp-encrypted");
Part pgp = message.findFirstPartByMimeType("application/pgp-encrypted");
if (pgp != null) {
Toast.makeText(mContext, R.string.pgp_mime_unsupported, Toast.LENGTH_LONG)
.show();
@ -241,12 +240,12 @@ public class MessageOpenPgpView extends LinearLayout {
public void run() {
try {
// get data String
Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
Part part = message.findFirstPartByMimeType("text/plain");
if (part == null) {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
part = message.findFirstPartByMimeType("text/html");
}
if (part != null) {
mData = MimeUtility.getTextFromPart(part);
mData = part.getText();
}
// wait for service to be bound

View File

@ -10,7 +10,7 @@ import android.widget.Toast;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.mail.internet.HtmlConverter;
import com.fsck.k9.helper.HtmlConverter;
public class MessageWebView extends RigidWebView {

View File

@ -46,7 +46,7 @@ import com.fsck.k9.fragment.MessageViewFragment;
import com.fsck.k9.helper.ClipboardManager;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.FileHelper;
import com.fsck.k9.mail.internet.HtmlConverter;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.internet;
package com.fsck.k9.activity;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
@ -8,6 +8,7 @@ import org.junit.runner.RunWith;
import com.fsck.k9.Account.QuoteStyle;
import com.fsck.k9.activity.TextBodyBuilder;
import com.fsck.k9.mail.internet.TextBody;
class TestingTextBodyBuilder extends TextBodyBuilder {

View File

@ -0,0 +1,94 @@
package com.fsck.k9.mail.internet;
import junit.framework.TestCase;
public class CharsetSupportTest extends TestCase {
public void testFixupCharset() throws Exception {
String charsetOnMail;
String expect;
charsetOnMail = "CP932";
expect = "shift_jis";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, new MimeMessage()));
// charsetOnMail = "koi8-u";
// expect = "koi8-r";
// assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, new MimeMessage()));
MimeMessage message;
message= new MimeMessage();
message.setHeader("From", "aaa@docomo.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@dwmail.jp");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@pdx.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@willcom.com");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@emnet.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@emobile.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@softbank.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-softbank-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@vodafone.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-softbank-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@disney.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-softbank-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@vertuclub.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-softbank-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@ezweb.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-kddi-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@ido.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-kddi-shift_jis-2007";
assertEquals(expect, CharsetSupport.fixupCharset(charsetOnMail, message));
}
}

View File

@ -8,15 +8,6 @@ import com.fsck.k9.mail.MessagingException;
import junit.framework.TestCase;
public class MimeUtilityTest extends TestCase {
protected void setUp() throws Exception {
super.setUp();
}
protected void tearDown() throws Exception {
super.tearDown();
}
public void testGetHeaderParameter() {
String result;
@ -55,93 +46,4 @@ public class MimeUtilityTest extends TestCase {
result = MimeUtility.getHeaderParameter("text/HTML ; charset=\"windows-1251\"", null);
assertEquals("text/HTML", result);
}
public void testFixupCharset() throws MessagingException {
String charsetOnMail;
String expect;
charsetOnMail = "CP932";
expect = "shift_jis";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, new MimeMessage()));
// charsetOnMail = "koi8-u";
// expect = "koi8-r";
// assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, new MimeMessage()));
MimeMessage message;
message= new MimeMessage();
message.setHeader("From", "aaa@docomo.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@dwmail.jp");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@pdx.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@willcom.com");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@emnet.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@emobile.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-docomo-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@softbank.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-softbank-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@vodafone.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-softbank-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@disney.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-softbank-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@vertuclub.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-softbank-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@ezweb.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-kddi-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
message = new MimeMessage();
message.setHeader("From", "aaa@ido.ne.jp");
charsetOnMail = "shift_jis";
expect = "x-kddi-shift_jis-2007";
assertEquals(expect, MimeUtility.fixupCharset(charsetOnMail, message));
}
}

View File

@ -1,4 +1,6 @@
package com.fsck.k9.mail.internet;
package com.fsck.k9.helper;
import com.fsck.k9.helper.HtmlConverter;
import junit.framework.TestCase;

View File

@ -14,11 +14,11 @@ import android.test.AndroidTestCase;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.internet.BinaryTempFileMessageBody;
import com.fsck.k9.mail.internet.CharsetSupport;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
public class MessageTest extends AndroidTestCase {
@ -356,7 +356,7 @@ public class MessageTest extends AndroidTestCase {
+ "End of test.\r\n");
textBody.setCharset("utf-8");
MimeBodyPart bodyPart = new MimeBodyPart(textBody, "text/plain");
MimeUtility.setCharset("utf-8", bodyPart);
CharsetSupport.setCharset("utf-8", bodyPart);
bodyPart.setEncoding(encoding);
return bodyPart;
}

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.internet;
package com.fsck.k9.mailstore;
import java.util.Date;
import java.util.Locale;
@ -9,9 +9,14 @@ import com.fsck.k9.activity.K9ActivityCommon;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.internet.MimeUtility.ViewableContainer;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.TextBody;
public class ViewablesTest extends AndroidTestCase {
import static com.fsck.k9.mailstore.LocalMessageExtractor.extractTextAndAttachments;
public class LocalMessageExtractorTest extends AndroidTestCase {
public void testSimplePlainTextMessage() throws MessagingException {
String bodyText = "K-9 Mail rocks :>";
@ -24,7 +29,7 @@ public class ViewablesTest extends AndroidTestCase {
message.setBody(body);
// Extract text
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
ViewableContainer container = extractTextAndAttachments(getContext(), message);
String expectedText = bodyText;
String expectedHtml =
@ -48,7 +53,7 @@ public class ViewablesTest extends AndroidTestCase {
message.setBody(body);
// Extract text
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
ViewableContainer container = extractTextAndAttachments(getContext(), message);
String expectedText = "K-9 Mail rocks :>";
String expectedHtml =
@ -78,7 +83,7 @@ public class ViewablesTest extends AndroidTestCase {
message.setBody(multipart);
// Extract text
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
ViewableContainer container = extractTextAndAttachments(getContext(), message);
String expectedText =
bodyText1 + "\r\n\r\n" +
@ -134,7 +139,7 @@ public class ViewablesTest extends AndroidTestCase {
message.setBody(multipart);
// Extract text
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
ViewableContainer container = extractTextAndAttachments(getContext(), message);
String expectedText =
bodyText +