mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-27 11:42:16 -05:00
Merge branch 'pr/374'
Encoding issues
This commit is contained in:
commit
b2013b6f5e
@ -78,10 +78,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.store.LocalStore;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBody;
|
||||
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentMessageBody;
|
||||
import com.fsck.k9.view.MessageWebView;
|
||||
import org.apache.james.mime4j.codec.EncoderUtil;
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
import org.htmlcleaner.CleanerProperties;
|
||||
import org.htmlcleaner.HtmlCleaner;
|
||||
import org.htmlcleaner.SimpleHtmlSerializer;
|
||||
@ -1471,11 +1472,17 @@ public class MessageCompose extends K9Activity implements OnClickListener {
|
||||
* @throws MessagingException
|
||||
*/
|
||||
private void addAttachmentsToMessage(final MimeMultipart mp) throws MessagingException {
|
||||
LocalAttachmentBody body;
|
||||
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
|
||||
Attachment attachment = (Attachment) mAttachments.getChildAt(i).getTag();
|
||||
|
||||
MimeBodyPart bp = new MimeBodyPart(
|
||||
new LocalStore.LocalAttachmentBody(attachment.uri, getApplication()));
|
||||
String contentType = attachment.contentType;
|
||||
if (MimeUtil.isMessage(contentType)) {
|
||||
body = new LocalAttachmentMessageBody(attachment.uri,
|
||||
getApplication());
|
||||
} else {
|
||||
body = new LocalAttachmentBody(attachment.uri, getApplication());
|
||||
}
|
||||
MimeBodyPart bp = new MimeBodyPart(body);
|
||||
|
||||
/*
|
||||
* Correctly encode the filename here. Otherwise the whole
|
||||
@ -1483,11 +1490,11 @@ public class MessageCompose extends K9Activity implements OnClickListener {
|
||||
* MimeHeader.writeTo().
|
||||
*/
|
||||
bp.addHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n name=\"%s\"",
|
||||
attachment.contentType,
|
||||
contentType,
|
||||
EncoderUtil.encodeIfNecessary(attachment.name,
|
||||
EncoderUtil.Usage.WORD_ENTITY, 7)));
|
||||
|
||||
bp.addHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
bp.setEncoding(MimeUtility.getEncodingforType(contentType));
|
||||
|
||||
/*
|
||||
* TODO: Oh the joys of MIME...
|
||||
|
@ -5,7 +5,10 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
||||
|
||||
public interface Body {
|
||||
public InputStream getInputStream() throws MessagingException;
|
||||
public void setEncoding(String encoding) throws UnavailableStorageException, MessagingException;
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException;
|
||||
}
|
||||
|
@ -11,4 +11,8 @@ public abstract class BodyPart implements Part {
|
||||
public void setParent(Multipart parent) {
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
public abstract void setEncoding(String encoding) throws MessagingException;
|
||||
|
||||
public abstract void setUsing7bitTransport() throws MessagingException;
|
||||
}
|
||||
|
29
src/com/fsck/k9/mail/CompositeBody.java
Normal file
29
src/com/fsck/k9/mail/CompositeBody.java
Normal file
@ -0,0 +1,29 @@
|
||||
package com.fsck.k9.mail;
|
||||
|
||||
|
||||
/**
|
||||
* A CompositeBody is a {@link Body} extension that can contain subparts that
|
||||
* may require recursing through or iterating over when converting the
|
||||
* CompositeBody from 8bit to 7bit encoding. The {@link Part} to which a
|
||||
* CompositeBody belongs is only permitted to use 8bit or 7bit content transfer
|
||||
* encoding for the CompositeBody.
|
||||
*
|
||||
*/
|
||||
public interface CompositeBody extends Body {
|
||||
|
||||
/**
|
||||
* Called just prior to transmission, once the type of transport is known to
|
||||
* be 7bit.
|
||||
* <p>
|
||||
* All subparts that are 8bit and of type {@link CompositeBody} will be
|
||||
* converted to 7bit and recursed. All supbparts that are 8bit but not
|
||||
* of type CompositeBody will be converted to quoted-printable. Bodies with
|
||||
* encodings other than 8bit remain unchanged.
|
||||
*
|
||||
* @throws MessagingException
|
||||
*
|
||||
*/
|
||||
|
||||
public abstract void setUsing7bitTransport() throws MessagingException;
|
||||
|
||||
}
|
@ -15,7 +15,7 @@ import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
|
||||
import com.fsck.k9.mail.store.UnavailableStorageException;
|
||||
|
||||
|
||||
public abstract class Message implements Part, Body {
|
||||
public abstract class Message implements Part, CompositeBody {
|
||||
private static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
|
||||
|
||||
private MessageReference mReference = null;
|
||||
@ -139,10 +139,6 @@ public abstract class Message implements Part, Body {
|
||||
|
||||
public abstract void setBody(Body body) throws MessagingException;
|
||||
|
||||
public boolean isMimeType(String mimeType) throws MessagingException {
|
||||
return getContentType().startsWith(mimeType);
|
||||
}
|
||||
|
||||
public abstract long getId();
|
||||
|
||||
public abstract String getPreview();
|
||||
@ -240,7 +236,7 @@ public abstract class Message implements Part, Body {
|
||||
|
||||
public void destroy() throws MessagingException {}
|
||||
|
||||
public abstract void setEncoding(String encoding) throws UnavailableStorageException;
|
||||
public abstract void setEncoding(String encoding) throws UnavailableStorageException, MessagingException;
|
||||
|
||||
public abstract void setCharset(String charset) throws MessagingException;
|
||||
|
||||
@ -298,4 +294,5 @@ public abstract class Message implements Part, Body {
|
||||
* </p>
|
||||
*/
|
||||
public abstract Message clone();
|
||||
public abstract void setUsing7bitTransport() throws MessagingException;
|
||||
}
|
||||
|
@ -3,11 +3,12 @@ package com.fsck.k9.mail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.fsck.k9.mail.internet.MimeHeader;
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
|
||||
import com.fsck.k9.mail.internet.MimeUtility;
|
||||
import com.fsck.k9.mail.internet.TextBody;
|
||||
|
||||
public abstract class Multipart implements Body {
|
||||
public abstract class Multipart implements CompositeBody {
|
||||
protected Part mParent;
|
||||
|
||||
protected ArrayList<BodyPart> mParts = new ArrayList<BodyPart>();
|
||||
@ -54,19 +55,14 @@ public abstract class Multipart implements Body {
|
||||
this.mParent = parent;
|
||||
}
|
||||
|
||||
public void setEncoding(String encoding) {
|
||||
for (BodyPart part : mParts) {
|
||||
try {
|
||||
Body body = part.getBody();
|
||||
if (body instanceof TextBody) {
|
||||
part.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
|
||||
((TextBody)body).setEncoding(encoding);
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
// Ignore
|
||||
}
|
||||
public void setEncoding(String encoding) throws MessagingException {
|
||||
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
|
||||
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
|
||||
throw new MessagingException(
|
||||
"Incompatible content-transfer-encoding applied to a CompositeBody");
|
||||
}
|
||||
|
||||
/* Nothing else to do. Each subpart has its own separate encoding */
|
||||
}
|
||||
|
||||
public void setCharset(String charset) throws MessagingException {
|
||||
|
@ -30,4 +30,17 @@ public interface Part {
|
||||
public void setBody(Body body) throws MessagingException;
|
||||
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException;
|
||||
|
||||
/**
|
||||
* Called just prior to transmission, once the type of transport is known to
|
||||
* be 7bit.
|
||||
* <p>
|
||||
* All bodies that are 8bit will be converted to 7bit and recursed if of
|
||||
* type {@link CompositeBody}, or will be converted to quoted-printable in all other
|
||||
* cases. Bodies with encodings other than 8bit remain unchanged.
|
||||
*
|
||||
* @throws MessagingException
|
||||
*
|
||||
*/
|
||||
public abstract void setUsing7bitTransport() throws MessagingException;
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import com.fsck.k9.mail.Body;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.filter.Base64OutputStream;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
@ -18,10 +20,16 @@ public class BinaryTempFileBody implements Body {
|
||||
|
||||
private File mFile;
|
||||
|
||||
String mEncoding = null;
|
||||
|
||||
public static void setTempDirectory(File tempDirectory) {
|
||||
mTempDirectory = tempDirectory;
|
||||
}
|
||||
|
||||
public void setEncoding(String encoding) throws MessagingException {
|
||||
mEncoding = encoding;
|
||||
}
|
||||
|
||||
public BinaryTempFileBody() {
|
||||
if (mTempDirectory == null) {
|
||||
throw new
|
||||
@ -46,11 +54,21 @@ public class BinaryTempFileBody implements Body {
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
InputStream in = getInputStream();
|
||||
try {
|
||||
Base64OutputStream base64Out = new Base64OutputStream(out);
|
||||
boolean closeStream = false;
|
||||
if (MimeUtil.isBase64Encoding(mEncoding)) {
|
||||
out = new Base64OutputStream(out);
|
||||
closeStream = true;
|
||||
} else if (MimeUtil.isQuotedPrintableEncoded(mEncoding)){
|
||||
out = new QuotedPrintableOutputStream(out, false);
|
||||
closeStream = true;
|
||||
}
|
||||
|
||||
try {
|
||||
IOUtils.copy(in, base64Out);
|
||||
IOUtils.copy(in, out);
|
||||
} finally {
|
||||
base64Out.close();
|
||||
if (closeStream) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
|
62
src/com/fsck/k9/mail/internet/BinaryTempFileMessageBody.java
Normal file
62
src/com/fsck/k9/mail/internet/BinaryTempFileMessageBody.java
Normal file
@ -0,0 +1,62 @@
|
||||
package com.fsck.k9.mail.internet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
|
||||
import com.fsck.k9.mail.CompositeBody;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
|
||||
/**
|
||||
* A {@link BinaryTempFileBody} extension containing a body of type
|
||||
* message/rfc822. This relates to a BinaryTempFileBody the same way that a
|
||||
* {@link LocalAttachmentMessageBody} relates to a {@link LocalAttachmentBody}.
|
||||
*
|
||||
*/
|
||||
public class BinaryTempFileMessageBody extends BinaryTempFileBody implements CompositeBody {
|
||||
|
||||
@Override
|
||||
public void setEncoding(String encoding) throws MessagingException {
|
||||
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
|
||||
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
|
||||
throw new MessagingException(
|
||||
"Incompatible content-transfer-encoding applied to a CompositeBody");
|
||||
}
|
||||
mEncoding = encoding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
InputStream in = getInputStream();
|
||||
try {
|
||||
if (MimeUtil.ENC_7BIT.equalsIgnoreCase(mEncoding)) {
|
||||
/*
|
||||
* If we knew the message was already 7bit clean, then it
|
||||
* could be sent along without processing. But since we
|
||||
* don't know, we recursively parse it.
|
||||
*/
|
||||
MimeMessage message = new MimeMessage(in, true);
|
||||
message.setUsing7bitTransport();
|
||||
message.writeTo(out);
|
||||
} else {
|
||||
IOUtils.copy(in, out);
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsing7bitTransport() throws MessagingException {
|
||||
/*
|
||||
* There's nothing to recurse into here, so there's nothing to do.
|
||||
* The enclosing BodyPart already called setEncoding(MimeUtil.ENC_7BIT). Once
|
||||
* writeTo() is called, the file with the rfc822 body will be opened
|
||||
* for reading and will then be recursed.
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
@ -3,12 +3,17 @@ package com.fsck.k9.mail.internet;
|
||||
|
||||
import com.fsck.k9.mail.Body;
|
||||
import com.fsck.k9.mail.BodyPart;
|
||||
import com.fsck.k9.mail.CompositeBody;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.Multipart;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
|
||||
/**
|
||||
* TODO this is a close approximation of Message, need to update along with
|
||||
@ -42,7 +47,7 @@ public class MimeBodyPart extends BodyPart {
|
||||
mHeader.addHeader(name, value);
|
||||
}
|
||||
|
||||
public void setHeader(String name, String value) throws MessagingException {
|
||||
public void setHeader(String name, String value) {
|
||||
mHeader.setHeader(name, value);
|
||||
}
|
||||
|
||||
@ -60,21 +65,35 @@ public class MimeBodyPart extends BodyPart {
|
||||
|
||||
public void setBody(Body body) throws MessagingException {
|
||||
this.mBody = body;
|
||||
if (body instanceof com.fsck.k9.mail.Multipart) {
|
||||
com.fsck.k9.mail.Multipart multipart = ((com.fsck.k9.mail.Multipart)body);
|
||||
if (body instanceof Multipart) {
|
||||
Multipart multipart = ((Multipart)body);
|
||||
multipart.setParent(this);
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
|
||||
String type = multipart.getContentType();
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, type);
|
||||
if ("multipart/signed".equalsIgnoreCase(type)) {
|
||||
setEncoding(MimeUtil.ENC_7BIT);
|
||||
} else {
|
||||
setEncoding(MimeUtil.ENC_8BIT);
|
||||
}
|
||||
} else if (body instanceof TextBody) {
|
||||
String contentType = String.format("%s;\n charset=utf-8", getMimeType());
|
||||
String contentType = String.format("%s;\r\n charset=utf-8", getMimeType());
|
||||
String name = MimeUtility.getHeaderParameter(getContentType(), "name");
|
||||
if (name != null) {
|
||||
contentType += String.format(";\n name=\"%s\"", name);
|
||||
contentType += String.format(";\r\n name=\"%s\"", name);
|
||||
}
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "quoted-printable");
|
||||
setEncoding(MimeUtil.ENC_8BIT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncoding(String encoding) throws MessagingException {
|
||||
if (mBody != null) {
|
||||
mBody.setEncoding(encoding);
|
||||
}
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
|
||||
}
|
||||
|
||||
public String getContentType() throws MessagingException {
|
||||
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
||||
return (contentType == null) ? "text/plain" : contentType;
|
||||
@ -103,7 +122,7 @@ public class MimeBodyPart extends BodyPart {
|
||||
}
|
||||
|
||||
public boolean isMimeType(String mimeType) throws MessagingException {
|
||||
return getMimeType().equals(mimeType);
|
||||
return getMimeType().equalsIgnoreCase(mimeType);
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
@ -122,4 +141,46 @@ public class MimeBodyPart extends BodyPart {
|
||||
mBody.writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsing7bitTransport() throws MessagingException {
|
||||
String type = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
||||
/*
|
||||
* We don't trust that a multipart/* will properly have an 8bit encoding
|
||||
* header if any of its subparts are 8bit, so we automatically recurse
|
||||
* (as long as its not multipart/signed).
|
||||
*/
|
||||
if (mBody instanceof CompositeBody
|
||||
&& !"multipart/signed".equalsIgnoreCase(type)) {
|
||||
setEncoding(MimeUtil.ENC_7BIT);
|
||||
// recurse
|
||||
((CompositeBody) mBody).setUsing7bitTransport();
|
||||
} else if (!MimeUtil.ENC_8BIT
|
||||
.equalsIgnoreCase(getFirstHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING))) {
|
||||
return;
|
||||
} else if (type != null
|
||||
&& (type.equalsIgnoreCase("multipart/signed") || type
|
||||
.toLowerCase(Locale.US).startsWith("message/"))) {
|
||||
/*
|
||||
* This shouldn't happen. In any case, it would be wrong to convert
|
||||
* them to some other encoding for 7bit transport.
|
||||
*
|
||||
* RFC 1847 says multipart/signed must be 7bit. It also says their
|
||||
* bodies must be treated as opaque, so we must not change the
|
||||
* encoding.
|
||||
*
|
||||
* We've dealt with (CompositeBody) type message/rfc822 above. Here
|
||||
* we must deal with all other message/* types. RFC 2045 says
|
||||
* message/* can only be 7bit or 8bit. RFC 2046 says unknown
|
||||
* message/* types must be treated as application/octet-stream,
|
||||
* which means we can't recurse into them. It also says that
|
||||
* existing subtypes message/partial and message/external must only
|
||||
* be 7bit, and that future subtypes "should be" 7bit.
|
||||
*/
|
||||
throw new MessagingException(
|
||||
"Unable to convert 8bit body part to 7bit");
|
||||
} else {
|
||||
setEncoding(MimeUtil.ENC_QUOTED_PRINTABLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,10 +22,12 @@ import org.apache.james.mime4j.parser.MimeStreamParser;
|
||||
import org.apache.james.mime4j.stream.BodyDescriptor;
|
||||
import org.apache.james.mime4j.stream.Field;
|
||||
import org.apache.james.mime4j.stream.MimeConfig;
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
|
||||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.mail.Body;
|
||||
import com.fsck.k9.mail.BodyPart;
|
||||
import com.fsck.k9.mail.CompositeBody;
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.Multipart;
|
||||
@ -69,7 +71,23 @@ public class MimeMessage extends Message {
|
||||
parse(in);
|
||||
}
|
||||
|
||||
protected void parse(InputStream in) throws IOException, MessagingException {
|
||||
/**
|
||||
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
|
||||
*
|
||||
* @param in
|
||||
* @param recurse A boolean indicating to recurse through all nested MimeMessage subparts.
|
||||
* @throws IOException
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public MimeMessage(InputStream in, boolean recurse) throws IOException, MessagingException {
|
||||
parse(in, true);
|
||||
}
|
||||
|
||||
protected void parse(InputStream in) throws IOException, MessagingException {
|
||||
parse(in, false);
|
||||
}
|
||||
|
||||
protected void parse(InputStream in, boolean recurse) throws IOException, MessagingException {
|
||||
mHeader.clear();
|
||||
mFrom = null;
|
||||
mTo = null;
|
||||
@ -92,6 +110,9 @@ public class MimeMessage extends Message {
|
||||
parserConfig.setMaxHeaderCount(-1); // Disable the check for header count.
|
||||
MimeStreamParser parser = new MimeStreamParser(parserConfig);
|
||||
parser.setContentHandler(new MimeMessageBuilder());
|
||||
if (recurse) {
|
||||
parser.setRecurse();
|
||||
}
|
||||
try {
|
||||
parser.parse(new EOLConvertingInputStream(in));
|
||||
} catch (MimeException me) {
|
||||
@ -143,7 +164,7 @@ public class MimeMessage extends Message {
|
||||
@Override
|
||||
public String getContentType() throws MessagingException {
|
||||
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
||||
return (contentType == null) ? "text/plain" : contentType.toLowerCase(Locale.US);
|
||||
return (contentType == null) ? "text/plain" : contentType;
|
||||
}
|
||||
|
||||
public String getDisposition() throws MessagingException {
|
||||
@ -156,6 +177,10 @@ public class MimeMessage extends Message {
|
||||
return MimeUtility.getHeaderParameter(getContentType(), null);
|
||||
}
|
||||
|
||||
public boolean isMimeType(String mimeType) throws MessagingException {
|
||||
return getMimeType().equalsIgnoreCase(mimeType);
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return mSize;
|
||||
}
|
||||
@ -355,11 +380,17 @@ public class MimeMessage extends Message {
|
||||
if (body instanceof Multipart) {
|
||||
Multipart multipart = ((Multipart)body);
|
||||
multipart.setParent(this);
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, multipart.getContentType());
|
||||
String type = multipart.getContentType();
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, type);
|
||||
if ("multipart/signed".equalsIgnoreCase(type)) {
|
||||
setEncoding(MimeUtil.ENC_7BIT);
|
||||
} else {
|
||||
setEncoding(MimeUtil.ENC_8BIT);
|
||||
}
|
||||
} else if (body instanceof TextBody) {
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\n charset=utf-8",
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, String.format("%s;\r\n charset=utf-8",
|
||||
getMimeType()));
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "quoted-printable");
|
||||
setEncoding(MimeUtil.ENC_8BIT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,13 +439,11 @@ public class MimeMessage extends Message {
|
||||
}
|
||||
|
||||
@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);
|
||||
public void setEncoding(String encoding) throws MessagingException {
|
||||
if (mBody != null) {
|
||||
mBody.setEncoding(encoding);
|
||||
}
|
||||
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -487,8 +516,9 @@ public class MimeMessage extends Message {
|
||||
|
||||
public void body(BodyDescriptor bd, InputStream in) throws IOException {
|
||||
expect(Part.class);
|
||||
Body body = MimeUtility.decodeBody(in, bd.getTransferEncoding());
|
||||
try {
|
||||
Body body = MimeUtility.decodeBody(in,
|
||||
bd.getTransferEncoding(), bd.getMimeType());
|
||||
((Part)stack.peek()).setBody(body);
|
||||
} catch (MessagingException me) {
|
||||
throw new Error(me);
|
||||
@ -597,4 +627,47 @@ public class MimeMessage extends Message {
|
||||
public boolean hasAttachments() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setUsing7bitTransport() throws MessagingException {
|
||||
String type = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
|
||||
/*
|
||||
* We don't trust that a multipart/* will properly have an 8bit encoding
|
||||
* header if any of its subparts are 8bit, so we automatically recurse
|
||||
* (as long as its not multipart/signed).
|
||||
*/
|
||||
if (mBody instanceof CompositeBody
|
||||
&& !"multipart/signed".equalsIgnoreCase(type)) {
|
||||
setEncoding(MimeUtil.ENC_7BIT);
|
||||
// recurse
|
||||
((CompositeBody) mBody).setUsing7bitTransport();
|
||||
} else if (!MimeUtil.ENC_8BIT
|
||||
.equalsIgnoreCase(getFirstHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING))) {
|
||||
return;
|
||||
} else if (type != null
|
||||
&& (type.equalsIgnoreCase("multipart/signed") || type
|
||||
.toLowerCase(Locale.US).startsWith("message/"))) {
|
||||
/*
|
||||
* This shouldn't happen. In any case, it would be wrong to convert
|
||||
* them to some other encoding for 7bit transport.
|
||||
*
|
||||
* RFC 1847 says multipart/signed must be 7bit. It also says their
|
||||
* bodies must be treated as opaque, so we must not change the
|
||||
* encoding.
|
||||
*
|
||||
* We've dealt with (CompositeBody) type message/rfc822 above. Here
|
||||
* we must deal with all other message/* types. RFC 2045 says
|
||||
* message/* can only be 7bit or 8bit. RFC 2046 says unknown
|
||||
* message/* types must be treated as application/octet-stream,
|
||||
* which means we can't recurse into them. It also says that
|
||||
* existing subtypes message/partial and message/external must only
|
||||
* be 7bit, and that future subtypes "should be" 7bit.
|
||||
*/
|
||||
throw new MessagingException(
|
||||
"Unable to convert 8bit body part to 7bit");
|
||||
} else {
|
||||
setEncoding(MimeUtil.ENC_QUOTED_PRINTABLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,4 +99,11 @@ public class MimeMultipart extends Multipart {
|
||||
public InputStream getInputStream() throws MessagingException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsing7bitTransport() throws MessagingException {
|
||||
for (BodyPart part : mParts) {
|
||||
part.setUsing7bitTransport();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import com.fsck.k9.mail.internet.BinaryTempFileBody.BinaryTempFileBodyInputStrea
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.james.mime4j.codec.Base64InputStream;
|
||||
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -1155,23 +1156,31 @@ public class MimeUtility {
|
||||
|
||||
/**
|
||||
* Removes any content transfer encoding from the stream and returns a Body.
|
||||
* @throws MessagingException
|
||||
*/
|
||||
public static Body decodeBody(InputStream in, String contentTransferEncoding)
|
||||
throws IOException {
|
||||
public static Body decodeBody(InputStream in,
|
||||
String contentTransferEncoding, String contentType)
|
||||
throws IOException, MessagingException {
|
||||
/*
|
||||
* We'll remove any transfer encoding by wrapping the stream.
|
||||
*/
|
||||
if (contentTransferEncoding != null) {
|
||||
contentTransferEncoding =
|
||||
MimeUtility.getHeaderParameter(contentTransferEncoding, null);
|
||||
if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding)) {
|
||||
if (MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(contentTransferEncoding)) {
|
||||
in = new QuotedPrintableInputStream(in);
|
||||
} else if ("base64".equalsIgnoreCase(contentTransferEncoding)) {
|
||||
} else if (MimeUtil.ENC_BASE64.equalsIgnoreCase(contentTransferEncoding)) {
|
||||
in = new Base64InputStream(in);
|
||||
}
|
||||
}
|
||||
|
||||
BinaryTempFileBody tempBody = new BinaryTempFileBody();
|
||||
BinaryTempFileBody tempBody;
|
||||
if (MimeUtil.isMessage(contentType)) {
|
||||
tempBody = new BinaryTempFileMessageBody();
|
||||
} else {
|
||||
tempBody = new BinaryTempFileBody();
|
||||
}
|
||||
tempBody.setEncoding(contentTransferEncoding);
|
||||
OutputStream out = tempBody.getOutputStream();
|
||||
try {
|
||||
IOUtils.copy(in, out);
|
||||
@ -2160,6 +2169,38 @@ public class MimeUtility {
|
||||
return canonicalizeMimeType(mimeType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a default content-transfer-encoding for use with a given content-type
|
||||
* when adding an unencoded attachment. It's possible that 8bit encodings
|
||||
* may later be converted to 7bit for 7bit transport.
|
||||
* <ul>
|
||||
* <li>null: base64
|
||||
* <li>message/rfc822: 8bit
|
||||
* <li>message/*: 7bit
|
||||
* <li>multipart/signed: 7bit
|
||||
* <li>multipart/*: 8bit
|
||||
* <li>*/*: base64
|
||||
* </ul>
|
||||
*
|
||||
* @param type
|
||||
* A String representing a MIME content-type
|
||||
* @return A String representing a MIME content-transfer-encoding
|
||||
*/
|
||||
public static String getEncodingforType(String type) {
|
||||
if (type == null) {
|
||||
return (MimeUtil.ENC_BASE64);
|
||||
} else if (MimeUtil.isMessage(type)) {
|
||||
return (MimeUtil.ENC_8BIT);
|
||||
} else if ("multipart/signed".equalsIgnoreCase(type) || type.toLowerCase(Locale.US).startsWith("message/")) {
|
||||
return (MimeUtil.ENC_7BIT);
|
||||
} else if (type.toLowerCase(Locale.US).startsWith("multipart/")) {
|
||||
return (MimeUtil.ENC_8BIT);
|
||||
} else {
|
||||
return (MimeUtil.ENC_BASE64);
|
||||
}
|
||||
}
|
||||
|
||||
private static Message getMessageFromPart(Part part) {
|
||||
while (part != null) {
|
||||
if (part instanceof Message)
|
||||
@ -3326,7 +3367,7 @@ public class MimeUtility {
|
||||
|
||||
public static void setCharset(String charset, Part part) throws MessagingException {
|
||||
part.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
|
||||
part.getMimeType() + ";\n charset=" + getExternalCharset(charset));
|
||||
part.getMimeType() + ";\r\n charset=" + getExternalCharset(charset));
|
||||
}
|
||||
|
||||
public static String getExternalCharset(String charset) {
|
||||
|
@ -7,6 +7,7 @@ import com.fsck.k9.mail.MessagingException;
|
||||
import java.io.*;
|
||||
|
||||
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
|
||||
public class TextBody implements Body {
|
||||
|
||||
@ -31,7 +32,7 @@ public class TextBody implements Body {
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
if (mBody != null) {
|
||||
byte[] bytes = mBody.getBytes(mCharset);
|
||||
if ("8bit".equals(mEncoding)) {
|
||||
if (MimeUtil.ENC_8BIT.equalsIgnoreCase(mEncoding)) {
|
||||
out.write(bytes);
|
||||
} else {
|
||||
QuotedPrintableOutputStream qp = new QuotedPrintableOutputStream(out, false);
|
||||
|
@ -1653,9 +1653,12 @@ public class ImapStore extends Store {
|
||||
String bodyString = (String)literal;
|
||||
InputStream bodyStream = new ByteArrayInputStream(bodyString.getBytes());
|
||||
|
||||
String contentTransferEncoding = part.getHeader(
|
||||
MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)[0];
|
||||
part.setBody(MimeUtility.decodeBody(bodyStream, contentTransferEncoding));
|
||||
String contentTransferEncoding = part
|
||||
.getHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)[0];
|
||||
String contentType = part
|
||||
.getHeader(MimeHeader.HEADER_CONTENT_TYPE)[0];
|
||||
part.setBody(MimeUtility.decodeBody(bodyStream,
|
||||
contentTransferEncoding, contentType));
|
||||
} else {
|
||||
// This shouldn't happen
|
||||
throw new MessagingException("Got FETCH response with bogus parameters");
|
||||
@ -3547,10 +3550,13 @@ public class ImapStore extends Store {
|
||||
ImapResponseParser.equalsIgnoreCase(response.get(1), "FETCH")) {
|
||||
//TODO: check for correct UID
|
||||
|
||||
String contentTransferEncoding = mPart.getHeader(
|
||||
MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)[0];
|
||||
String contentTransferEncoding = mPart
|
||||
.getHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)[0];
|
||||
String contentType = mPart
|
||||
.getHeader(MimeHeader.HEADER_CONTENT_TYPE)[0];
|
||||
|
||||
return MimeUtility.decodeBody(literal, contentTransferEncoding);
|
||||
return MimeUtility.decodeBody(literal, contentTransferEncoding,
|
||||
contentType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ContentResolver;
|
||||
@ -51,6 +53,7 @@ import com.fsck.k9.helper.Utility;
|
||||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.mail.Body;
|
||||
import com.fsck.k9.mail.BodyPart;
|
||||
import com.fsck.k9.mail.CompositeBody;
|
||||
import com.fsck.k9.mail.FetchProfile;
|
||||
import com.fsck.k9.mail.Flag;
|
||||
import com.fsck.k9.mail.Folder;
|
||||
@ -1936,6 +1939,7 @@ public class LocalStore extends Store implements Serializable {
|
||||
String contentUri = cursor.getString(5);
|
||||
String contentId = cursor.getString(6);
|
||||
String contentDisposition = cursor.getString(7);
|
||||
String encoding = MimeUtility.getEncodingforType(type);
|
||||
Body body = null;
|
||||
|
||||
if (contentDisposition == null) {
|
||||
@ -1943,11 +1947,19 @@ public class LocalStore extends Store implements Serializable {
|
||||
}
|
||||
|
||||
if (contentUri != null) {
|
||||
body = new LocalAttachmentBody(Uri.parse(contentUri), mApplication);
|
||||
if (MimeUtil.isMessage(type)) {
|
||||
body = new LocalAttachmentMessageBody(
|
||||
Uri.parse(contentUri),
|
||||
mApplication);
|
||||
} else {
|
||||
body = new LocalAttachmentBody(
|
||||
Uri.parse(contentUri),
|
||||
mApplication);
|
||||
}
|
||||
}
|
||||
|
||||
MimeBodyPart bp = new LocalAttachmentBodyPart(body, id);
|
||||
bp.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
bp.setEncoding(encoding);
|
||||
if (name != null) {
|
||||
bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
|
||||
String.format("%s;\n name=\"%s\"",
|
||||
@ -2852,7 +2864,13 @@ public class LocalStore extends Store implements Serializable {
|
||||
contentUri = AttachmentProvider.getAttachmentUri(
|
||||
mAccount,
|
||||
attachmentId);
|
||||
attachment.setBody(new LocalAttachmentBody(contentUri, mApplication));
|
||||
if (MimeUtil.isMessage(attachment.getMimeType())) {
|
||||
attachment.setBody(new LocalAttachmentMessageBody(
|
||||
contentUri, mApplication));
|
||||
} else {
|
||||
attachment.setBody(new LocalAttachmentBody(
|
||||
contentUri, mApplication));
|
||||
}
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
|
||||
db.update("attachments", cv, "id = ?", new String[]
|
||||
@ -3988,6 +4006,7 @@ public class LocalStore extends Store implements Serializable {
|
||||
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||
private Application mApplication;
|
||||
private Uri mUri;
|
||||
protected String mEncoding;
|
||||
|
||||
public LocalAttachmentBody(Uri uri, Application application) {
|
||||
mApplication = application;
|
||||
@ -4011,11 +4030,21 @@ public class LocalStore extends Store implements Serializable {
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
InputStream in = getInputStream();
|
||||
try {
|
||||
Base64OutputStream base64Out = new Base64OutputStream(out);
|
||||
boolean closeStream = false;
|
||||
if (MimeUtil.isBase64Encoding(mEncoding)) {
|
||||
out = new Base64OutputStream(out);
|
||||
closeStream = true;
|
||||
} else if (MimeUtil.isQuotedPrintableEncoded(mEncoding)){
|
||||
out = new QuotedPrintableOutputStream(out, false);
|
||||
closeStream = true;
|
||||
}
|
||||
|
||||
try {
|
||||
IOUtils.copy(in, base64Out);
|
||||
IOUtils.copy(in, out);
|
||||
} finally {
|
||||
base64Out.close();
|
||||
if (closeStream) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
@ -4025,6 +4054,64 @@ public class LocalStore extends Store implements Serializable {
|
||||
public Uri getContentUri() {
|
||||
return mUri;
|
||||
}
|
||||
|
||||
public void setEncoding(String encoding) throws MessagingException {
|
||||
mEncoding = encoding;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link LocalAttachmentBody} extension containing a message/rfc822 type body
|
||||
*
|
||||
*/
|
||||
public static class LocalAttachmentMessageBody extends LocalAttachmentBody implements CompositeBody {
|
||||
|
||||
public LocalAttachmentMessageBody(Uri uri, Application application) {
|
||||
super(uri, application);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream out) throws IOException,
|
||||
MessagingException {
|
||||
InputStream in = getInputStream();
|
||||
try {
|
||||
if (MimeUtil.ENC_7BIT.equalsIgnoreCase(mEncoding)) {
|
||||
/*
|
||||
* If we knew the message was already 7bit clean, then it
|
||||
* could be sent along without processing. But since we
|
||||
* don't know, we recursively parse it.
|
||||
*/
|
||||
MimeMessage message = new MimeMessage(in, true);
|
||||
message.setUsing7bitTransport();
|
||||
message.writeTo(out);
|
||||
} else {
|
||||
IOUtils.copy(in, out);
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUsing7bitTransport() throws MessagingException {
|
||||
/*
|
||||
* There's nothing to recurse into here, so there's nothing to do.
|
||||
* The enclosing BodyPart already called setEncoding(MimeUtil.ENC_7BIT). Once
|
||||
* writeTo() is called, the file with the rfc822 body will be opened
|
||||
* for reading and will then be recursed.
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncoding(String encoding) throws MessagingException {
|
||||
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
|
||||
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
|
||||
throw new MessagingException(
|
||||
"Incompatible content-transfer-encoding applied to a CompositeBody");
|
||||
}
|
||||
mEncoding = encoding;
|
||||
}
|
||||
}
|
||||
|
||||
static class ThreadInfo {
|
||||
|
@ -477,7 +477,9 @@ public class SmtpTransport extends Transport {
|
||||
close();
|
||||
open();
|
||||
|
||||
message.setEncoding(m8bitEncodingAllowed ? "8bit" : null);
|
||||
if (!m8bitEncodingAllowed) {
|
||||
message.setUsing7bitTransport();
|
||||
}
|
||||
// If the message has attachments and our server has told us about a limit on
|
||||
// the size of messages, count the message's size before sending it
|
||||
if (mLargestAcceptableMessage > 0 && ((LocalMessage)message).hasAttachments()) {
|
||||
@ -490,8 +492,8 @@ public class SmtpTransport extends Transport {
|
||||
|
||||
Address[] from = message.getFrom();
|
||||
try {
|
||||
//TODO: Add BODY=8BITMIME parameter if appropriate?
|
||||
executeSimpleCommand("MAIL FROM:" + "<" + from[0].getAddress() + ">");
|
||||
executeSimpleCommand("MAIL FROM:" + "<" + from[0].getAddress() + ">"
|
||||
+ (m8bitEncodingAllowed ? " BODY=8BITMIME" : ""));
|
||||
for (String address : addresses) {
|
||||
executeSimpleCommand("RCPT TO:" + "<" + address + ">");
|
||||
}
|
||||
|
380
tests/src/com/fsck/k9/mail/MessageTest.java
Normal file
380
tests/src/com/fsck/k9/mail/MessageTest.java
Normal file
@ -0,0 +1,380 @@
|
||||
package com.fsck.k9.mail;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.james.mime4j.codec.Base64InputStream;
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
|
||||
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.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 {
|
||||
|
||||
private static final String EIGHT_BIT_RESULT =
|
||||
"From: from@example.com\r\n"
|
||||
+ "To: to@example.com\r\n"
|
||||
+ "Subject: Test Message\r\n"
|
||||
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||
+ "MIME-Version: 1.0\r\n"
|
||||
+ "Content-Type: multipart/mixed; boundary=\"----Boundary103\"\r\n"
|
||||
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary103\r\n"
|
||||
+ "Content-Type: text/plain;\r\n"
|
||||
+ " charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing.\r\n"
|
||||
+ "This is a text body with some greek characters.\r\n"
|
||||
+ "αβγδεζηθ\r\n"
|
||||
+ "End of test.\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary103\r\n"
|
||||
+ "Content-Type: text/plain;\r\n"
|
||||
+ " charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing=2E\r\n"
|
||||
+ "This is a text body with some greek characters=2E\r\n"
|
||||
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||
+ "End of test=2E\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary103\r\n"
|
||||
+ "Content-Type: application/octet-stream\r\n"
|
||||
+ "Content-Transfer-Encoding: base64\r\n"
|
||||
+ "\r\n"
|
||||
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary103\r\n"
|
||||
+ "Content-Type: message/rfc822\r\n"
|
||||
+ "Content-Disposition: attachment\r\n"
|
||||
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "From: from@example.com\r\n"
|
||||
+ "To: to@example.com\r\n"
|
||||
+ "Subject: Test Message\r\n"
|
||||
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||
+ "MIME-Version: 1.0\r\n"
|
||||
+ "Content-Type: multipart/mixed; boundary=\"----Boundary102\"\r\n"
|
||||
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary102\r\n"
|
||||
+ "Content-Type: text/plain;\r\n"
|
||||
+ " charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing.\r\n"
|
||||
+ "This is a text body with some greek characters.\r\n"
|
||||
+ "αβγδεζηθ\r\n"
|
||||
+ "End of test.\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary102\r\n"
|
||||
+ "Content-Type: text/plain;\r\n"
|
||||
+ " charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing=2E\r\n"
|
||||
+ "This is a text body with some greek characters=2E\r\n"
|
||||
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||
+ "End of test=2E\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary102\r\n"
|
||||
+ "Content-Type: application/octet-stream\r\n"
|
||||
+ "Content-Transfer-Encoding: base64\r\n"
|
||||
+ "\r\n"
|
||||
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary102\r\n"
|
||||
+ "Content-Type: message/rfc822\r\n"
|
||||
+ "Content-Disposition: attachment\r\n"
|
||||
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "From: from@example.com\r\n"
|
||||
+ "To: to@example.com\r\n"
|
||||
+ "Subject: Test Message\r\n"
|
||||
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||
+ "MIME-Version: 1.0\r\n"
|
||||
+ "Content-Type: multipart/mixed; boundary=\"----Boundary101\"\r\n"
|
||||
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary101\r\n"
|
||||
+ "Content-Type: text/plain;\r\n"
|
||||
+ " charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: 8bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing.\r\n"
|
||||
+ "This is a text body with some greek characters.\r\n"
|
||||
+ "αβγδεζηθ\r\n"
|
||||
+ "End of test.\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary101\r\n"
|
||||
+ "Content-Type: text/plain;\r\n"
|
||||
+ " charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing=2E\r\n"
|
||||
+ "This is a text body with some greek characters=2E\r\n"
|
||||
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||
+ "End of test=2E\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary101\r\n"
|
||||
+ "Content-Type: application/octet-stream\r\n"
|
||||
+ "Content-Transfer-Encoding: base64\r\n"
|
||||
+ "\r\n"
|
||||
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary101--\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary102--\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary103--\r\n";
|
||||
|
||||
private static final String SEVEN_BIT_RESULT =
|
||||
"From: from@example.com\r\n"
|
||||
+ "To: to@example.com\r\n"
|
||||
+ "Subject: Test Message\r\n"
|
||||
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||
+ "MIME-Version: 1.0\r\n"
|
||||
+ "Content-Type: multipart/mixed; boundary=\"----Boundary103\"\r\n"
|
||||
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary103\r\n"
|
||||
+ "Content-Type: text/plain;\r\n"
|
||||
+ " charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing=2E\r\n"
|
||||
+ "This is a text body with some greek characters=2E\r\n"
|
||||
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||
+ "End of test=2E\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary103\r\n"
|
||||
+ "Content-Type: text/plain;\r\n"
|
||||
+ " charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing=2E\r\n"
|
||||
+ "This is a text body with some greek characters=2E\r\n"
|
||||
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||
+ "End of test=2E\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary103\r\n"
|
||||
+ "Content-Type: application/octet-stream\r\n"
|
||||
+ "Content-Transfer-Encoding: base64\r\n"
|
||||
+ "\r\n"
|
||||
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary103\r\n"
|
||||
+ "Content-Type: message/rfc822\r\n"
|
||||
+ "Content-Disposition: attachment\r\n"
|
||||
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "From: from@example.com\r\n"
|
||||
+ "To: to@example.com\r\n"
|
||||
+ "Subject: Test Message\r\n"
|
||||
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||
+ "MIME-Version: 1.0\r\n"
|
||||
+ "Content-Type: multipart/mixed; boundary=\"----Boundary102\"\r\n"
|
||||
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary102\r\n"
|
||||
+ "Content-Type: text/plain; charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing=2E\r\n"
|
||||
+ "This is a text body with some greek characters=2E\r\n"
|
||||
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||
+ "End of test=2E\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary102\r\n"
|
||||
+ "Content-Type: text/plain; charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing=2E\r\n"
|
||||
+ "This is a text body with some greek characters=2E\r\n"
|
||||
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||
+ "End of test=2E\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary102\r\n"
|
||||
+ "Content-Type: application/octet-stream\r\n"
|
||||
+ "Content-Transfer-Encoding: base64\r\n"
|
||||
+ "\r\n"
|
||||
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary102\r\n"
|
||||
+ "Content-Type: message/rfc822\r\n"
|
||||
+ "Content-Disposition: attachment\r\n"
|
||||
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "From: from@example.com\r\n"
|
||||
+ "To: to@example.com\r\n"
|
||||
+ "Subject: Test Message\r\n"
|
||||
+ "Date: Wed, 28 Aug 2013 08:51:09 -0400\r\n"
|
||||
+ "MIME-Version: 1.0\r\n"
|
||||
+ "Content-Type: multipart/mixed; boundary=\"----Boundary101\"\r\n"
|
||||
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary101\r\n"
|
||||
+ "Content-Type: text/plain; charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing=2E\r\n"
|
||||
+ "This is a text body with some greek characters=2E\r\n"
|
||||
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||
+ "End of test=2E\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary101\r\n"
|
||||
+ "Content-Type: text/plain; charset=utf-8\r\n"
|
||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||
+ "\r\n"
|
||||
+ "Testing=2E\r\n"
|
||||
+ "This is a text body with some greek characters=2E\r\n"
|
||||
+ "=CE=B1=CE=B2=CE=B3=CE=B4=CE=B5=CE=B6=CE=B7=CE=B8\r\n"
|
||||
+ "End of test=2E\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary101\r\n"
|
||||
+ "Content-Type: application/octet-stream\r\n"
|
||||
+ "Content-Transfer-Encoding: base64\r\n"
|
||||
+ "\r\n"
|
||||
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary101--\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary102--\r\n"
|
||||
+ "\r\n"
|
||||
+ "------Boundary103--\r\n";
|
||||
|
||||
private int mMimeBoundary;
|
||||
|
||||
public MessageTest() {
|
||||
super();
|
||||
}
|
||||
|
||||
public void testMessage() throws MessagingException, IOException {
|
||||
MimeMessage message;
|
||||
ByteArrayOutputStream out;
|
||||
|
||||
BinaryTempFileBody.setTempDirectory(getContext().getCacheDir());
|
||||
|
||||
mMimeBoundary = 101;
|
||||
message = nestedMessage(nestedMessage(sampleMessage()));
|
||||
out = new ByteArrayOutputStream();
|
||||
message.writeTo(out);
|
||||
assertEquals(EIGHT_BIT_RESULT, out.toString());
|
||||
|
||||
mMimeBoundary = 101;
|
||||
message = nestedMessage(nestedMessage(sampleMessage()));
|
||||
message.setUsing7bitTransport();
|
||||
out = new ByteArrayOutputStream();
|
||||
message.writeTo(out);
|
||||
assertEquals(SEVEN_BIT_RESULT, out.toString());
|
||||
}
|
||||
|
||||
private MimeMessage nestedMessage(MimeMessage subMessage)
|
||||
throws MessagingException, IOException {
|
||||
BinaryTempFileMessageBody tempMessageBody = new BinaryTempFileMessageBody();
|
||||
tempMessageBody.setEncoding(MimeUtil.ENC_8BIT);
|
||||
|
||||
OutputStream out = tempMessageBody.getOutputStream();
|
||||
try {
|
||||
subMessage.writeTo(out);
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
|
||||
MimeBodyPart bodyPart = new MimeBodyPart(tempMessageBody, "message/rfc822");
|
||||
bodyPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, "attachment");
|
||||
bodyPart.setEncoding(MimeUtil.ENC_8BIT);
|
||||
|
||||
MimeMessage parentMessage = sampleMessage();
|
||||
((Multipart) parentMessage.getBody()).addBodyPart(bodyPart);
|
||||
|
||||
return parentMessage;
|
||||
}
|
||||
|
||||
private MimeMessage sampleMessage() throws MessagingException, IOException {
|
||||
MimeMessage message = new MimeMessage();
|
||||
message.setFrom(new Address("from@example.com"));
|
||||
message.setRecipient(RecipientType.TO, new Address("to@example.com"));
|
||||
message.setSubject("Test Message");
|
||||
message.setHeader("Date", "Wed, 28 Aug 2013 08:51:09 -0400");
|
||||
message.setEncoding(MimeUtil.ENC_8BIT);
|
||||
|
||||
NonRandomMimeMultipartTest multipartBody = new NonRandomMimeMultipartTest();
|
||||
multipartBody.setSubType("mixed");
|
||||
multipartBody.addBodyPart(textBodyPart(MimeUtil.ENC_8BIT));
|
||||
multipartBody.addBodyPart(textBodyPart(MimeUtil.ENC_QUOTED_PRINTABLE));
|
||||
multipartBody.addBodyPart(binaryBodyPart());
|
||||
message.setBody(multipartBody);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private MimeBodyPart binaryBodyPart() throws IOException,
|
||||
MessagingException {
|
||||
String encodedTestString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
+ "abcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
BinaryTempFileBody tempFileBody = new BinaryTempFileBody();
|
||||
|
||||
InputStream in = new Base64InputStream(new ByteArrayInputStream(
|
||||
encodedTestString.getBytes("UTF-8")));
|
||||
|
||||
OutputStream out = tempFileBody.getOutputStream();
|
||||
try {
|
||||
IOUtils.copy(in, out);
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
|
||||
MimeBodyPart bodyPart = new MimeBodyPart(tempFileBody,
|
||||
"application/octet-stream");
|
||||
bodyPart.setEncoding(MimeUtil.ENC_BASE64);
|
||||
|
||||
return bodyPart;
|
||||
}
|
||||
|
||||
private MimeBodyPart textBodyPart(String encoding)
|
||||
throws MessagingException {
|
||||
TextBody textBody = new TextBody(
|
||||
"Testing.\r\n"
|
||||
+ "This is a text body with some greek characters.\r\n"
|
||||
+ "αβγδεζηθ\r\n"
|
||||
+ "End of test.\r\n");
|
||||
textBody.setCharset("utf-8");
|
||||
MimeBodyPart bodyPart = new MimeBodyPart(textBody, "text/plain");
|
||||
MimeUtility.setCharset("utf-8", bodyPart);
|
||||
bodyPart.setEncoding(encoding);
|
||||
return bodyPart;
|
||||
}
|
||||
|
||||
private class NonRandomMimeMultipartTest extends MimeMultipart {
|
||||
|
||||
public NonRandomMimeMultipartTest() throws MessagingException {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateBoundary() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("----Boundary");
|
||||
sb.append(Integer.toString(mMimeBoundary++));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user