mirror of
https://github.com/moparisthebest/k-9
synced 2025-02-19 20:21:45 -05:00
Merge pull request #21 from k9mail/reconstruct_original_message_in_memory
Reconstruct original message in memory
This commit is contained in:
commit
987b8b17b1
@ -104,6 +104,7 @@ import com.fsck.k9.mail.Part;
|
|||||||
import com.fsck.k9.mail.internet.MimeBodyPart;
|
import com.fsck.k9.mail.internet.MimeBodyPart;
|
||||||
import com.fsck.k9.mail.internet.MimeHeader;
|
import com.fsck.k9.mail.internet.MimeHeader;
|
||||||
import com.fsck.k9.mail.internet.MimeMessage;
|
import com.fsck.k9.mail.internet.MimeMessage;
|
||||||
|
import com.fsck.k9.mail.internet.MimeMessageHelper;
|
||||||
import com.fsck.k9.mail.internet.MimeMultipart;
|
import com.fsck.k9.mail.internet.MimeMultipart;
|
||||||
import com.fsck.k9.mail.internet.MimeUtility;
|
import com.fsck.k9.mail.internet.MimeUtility;
|
||||||
import com.fsck.k9.mail.internet.TextBody;
|
import com.fsck.k9.mail.internet.TextBody;
|
||||||
@ -1404,10 +1405,10 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
|||||||
MimeMultipart mp = new MimeMultipart();
|
MimeMultipart mp = new MimeMultipart();
|
||||||
mp.addBodyPart(new MimeBodyPart(composedMimeMessage));
|
mp.addBodyPart(new MimeBodyPart(composedMimeMessage));
|
||||||
addAttachmentsToMessage(mp);
|
addAttachmentsToMessage(mp);
|
||||||
message.setBody(mp);
|
MimeMessageHelper.setBody(message, mp);
|
||||||
} else {
|
} else {
|
||||||
// If no attachments, our multipart/alternative part is the only one we need.
|
// If no attachments, our multipart/alternative part is the only one we need.
|
||||||
message.setBody(composedMimeMessage);
|
MimeMessageHelper.setBody(message, composedMimeMessage);
|
||||||
}
|
}
|
||||||
} else if (mMessageFormat == SimpleMessageFormat.TEXT) {
|
} else if (mMessageFormat == SimpleMessageFormat.TEXT) {
|
||||||
// Text-only message.
|
// Text-only message.
|
||||||
@ -1415,10 +1416,10 @@ public class MessageCompose extends K9Activity implements OnClickListener,
|
|||||||
MimeMultipart mp = new MimeMultipart();
|
MimeMultipart mp = new MimeMultipart();
|
||||||
mp.addBodyPart(new MimeBodyPart(body, "text/plain"));
|
mp.addBodyPart(new MimeBodyPart(body, "text/plain"));
|
||||||
addAttachmentsToMessage(mp);
|
addAttachmentsToMessage(mp);
|
||||||
message.setBody(mp);
|
MimeMessageHelper.setBody(message, mp);
|
||||||
} else {
|
} else {
|
||||||
// No attachments to include, just stick the text body in the message and call it good.
|
// No attachments to include, just stick the text body in the message and call it good.
|
||||||
message.setBody(body);
|
MimeMessageHelper.setBody(message, body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +79,7 @@ import com.fsck.k9.mail.Pusher;
|
|||||||
import com.fsck.k9.mail.Store;
|
import com.fsck.k9.mail.Store;
|
||||||
import com.fsck.k9.mail.Transport;
|
import com.fsck.k9.mail.Transport;
|
||||||
import com.fsck.k9.mail.internet.MimeMessage;
|
import com.fsck.k9.mail.internet.MimeMessage;
|
||||||
|
import com.fsck.k9.mail.internet.MimeMessageHelper;
|
||||||
import com.fsck.k9.mail.internet.MimeUtility;
|
import com.fsck.k9.mail.internet.MimeUtility;
|
||||||
import com.fsck.k9.mail.internet.TextBody;
|
import com.fsck.k9.mail.internet.TextBody;
|
||||||
import com.fsck.k9.mail.store.local.LocalFolder;
|
import com.fsck.k9.mail.store.local.LocalFolder;
|
||||||
@ -2715,7 +2716,7 @@ public class MessagingController implements Runnable {
|
|||||||
LocalFolder localFolder = (LocalFolder)localStore.getFolder(account.getErrorFolderName());
|
LocalFolder localFolder = (LocalFolder)localStore.getFolder(account.getErrorFolderName());
|
||||||
MimeMessage message = new MimeMessage();
|
MimeMessage message = new MimeMessage();
|
||||||
|
|
||||||
message.setBody(new TextBody(body));
|
MimeMessageHelper.setBody(message, new TextBody(body));
|
||||||
message.setFlag(Flag.X_DOWNLOADED_FULL, true);
|
message.setFlag(Flag.X_DOWNLOADED_FULL, true);
|
||||||
message.setSubject(subject);
|
message.setSubject(subject);
|
||||||
|
|
||||||
@ -3208,7 +3209,7 @@ public class MessagingController implements Runnable {
|
|||||||
|
|
||||||
//FIXME: This is an ugly hack that won't be needed once the Message objects have been united.
|
//FIXME: This is an ugly hack that won't be needed once the Message objects have been united.
|
||||||
Message remoteMessage = remoteFolder.getMessage(message.getUid());
|
Message remoteMessage = remoteFolder.getMessage(message.getUid());
|
||||||
remoteMessage.setBody(message.getBody());
|
MimeMessageHelper.setBody(remoteMessage, message.getBody());
|
||||||
remoteFolder.fetchPart(remoteMessage, part, null);
|
remoteFolder.fetchPart(remoteMessage, part, null);
|
||||||
|
|
||||||
localFolder.updateMessage((LocalMessage)message);
|
localFolder.updateMessage((LocalMessage)message);
|
||||||
|
@ -135,6 +135,9 @@ public abstract class Message implements Part, CompositeBody {
|
|||||||
@Override
|
@Override
|
||||||
public abstract void addHeader(String name, String value) throws MessagingException;
|
public abstract void addHeader(String name, String value) throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void addRawHeader(String name, String raw) throws MessagingException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract void setHeader(String name, String value) throws MessagingException;
|
public abstract void setHeader(String name, String value) throws MessagingException;
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ import java.io.OutputStream;
|
|||||||
public interface Part {
|
public interface Part {
|
||||||
public void addHeader(String name, String value) throws MessagingException;
|
public void addHeader(String name, String value) throws MessagingException;
|
||||||
|
|
||||||
|
public void addRawHeader(String name, String raw) throws MessagingException;
|
||||||
|
|
||||||
public void removeHeader(String name) throws MessagingException;
|
public void removeHeader(String name) throws MessagingException;
|
||||||
|
|
||||||
public void setHeader(String name, String value) throws MessagingException;
|
public void setHeader(String name, String value) throws MessagingException;
|
||||||
|
@ -15,7 +15,7 @@ import java.io.*;
|
|||||||
* and writeTo one time. After writeTo is called, or the InputStream returned from
|
* and writeTo one time. After writeTo is called, or the InputStream returned from
|
||||||
* getInputStream is closed the file is deleted and the Body should be considered disposed of.
|
* getInputStream is closed the file is deleted and the Body should be considered disposed of.
|
||||||
*/
|
*/
|
||||||
public class BinaryTempFileBody implements Body {
|
public class BinaryTempFileBody implements RawDataBody {
|
||||||
private static File mTempDirectory;
|
private static File mTempDirectory;
|
||||||
|
|
||||||
private File mFile;
|
private File mFile;
|
||||||
@ -26,15 +26,56 @@ public class BinaryTempFileBody implements Body {
|
|||||||
mTempDirectory = tempDirectory;
|
mTempDirectory = tempDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEncoding(String encoding) throws MessagingException {
|
@Override
|
||||||
mEncoding = encoding;
|
public String getEncoding() {
|
||||||
|
return mEncoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BinaryTempFileBody() {
|
public void setEncoding(String encoding) throws MessagingException {
|
||||||
if (mTempDirectory == null) {
|
if (mEncoding != null && mEncoding.equalsIgnoreCase(encoding)) {
|
||||||
throw new
|
return;
|
||||||
RuntimeException("setTempDirectory has not been called on BinaryTempFileBody!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The encoding changed, so we need to convert the message
|
||||||
|
if (!MimeUtil.ENC_8BIT.equalsIgnoreCase(mEncoding)) {
|
||||||
|
throw new RuntimeException("Can't convert from encoding: " + mEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
File newFile = File.createTempFile("body", null, mTempDirectory);
|
||||||
|
OutputStream out = new FileOutputStream(newFile);
|
||||||
|
try {
|
||||||
|
if (MimeUtil.ENC_QUOTED_PRINTABLE.equals(encoding)) {
|
||||||
|
out = new QuotedPrintableOutputStream(out, false);
|
||||||
|
} else if (MimeUtil.ENC_BASE64.equals(encoding)) {
|
||||||
|
out = new Base64OutputStream(out);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Target encoding not supported: " + encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream in = getInputStream();
|
||||||
|
try {
|
||||||
|
IOUtils.copy(in, out);
|
||||||
|
} finally {
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
mFile = newFile;
|
||||||
|
mEncoding = encoding;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MessagingException("Unable to convert body", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BinaryTempFileBody(String encoding) {
|
||||||
|
if (mTempDirectory == null) {
|
||||||
|
throw new RuntimeException("setTempDirectory has not been called on BinaryTempFileBody!");
|
||||||
|
}
|
||||||
|
|
||||||
|
mEncoding = encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OutputStream getOutputStream() throws IOException {
|
public OutputStream getOutputStream() throws IOException {
|
||||||
@ -54,22 +95,7 @@ public class BinaryTempFileBody implements Body {
|
|||||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||||
InputStream in = getInputStream();
|
InputStream in = getInputStream();
|
||||||
try {
|
try {
|
||||||
boolean closeStream = false;
|
IOUtils.copy(in, out);
|
||||||
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, out);
|
|
||||||
} finally {
|
|
||||||
if (closeStream) {
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
in.close();
|
in.close();
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,10 @@ import com.fsck.k9.mail.MessagingException;
|
|||||||
*/
|
*/
|
||||||
public class BinaryTempFileMessageBody extends BinaryTempFileBody implements CompositeBody {
|
public class BinaryTempFileMessageBody extends BinaryTempFileBody implements CompositeBody {
|
||||||
|
|
||||||
|
public BinaryTempFileMessageBody(String encoding) {
|
||||||
|
super(encoding);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setEncoding(String encoding) throws MessagingException {
|
public void setEncoding(String encoding) throws MessagingException {
|
||||||
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
|
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
|
||||||
|
@ -35,7 +35,7 @@ public class MimeBodyPart extends BodyPart {
|
|||||||
if (mimeType != null) {
|
if (mimeType != null) {
|
||||||
addHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
|
addHeader(MimeHeader.HEADER_CONTENT_TYPE, mimeType);
|
||||||
}
|
}
|
||||||
setBody(body);
|
MimeMessageHelper.setBody(this, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFirstHeader(String name) {
|
private String getFirstHeader(String name) {
|
||||||
@ -47,6 +47,11 @@ public class MimeBodyPart extends BodyPart {
|
|||||||
mHeader.addHeader(name, value);
|
mHeader.addHeader(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRawHeader(String name, String raw) {
|
||||||
|
mHeader.addRawHeader(name, raw);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setHeader(String name, String value) {
|
public void setHeader(String name, String value) {
|
||||||
mHeader.setHeader(name, value);
|
mHeader.setHeader(name, value);
|
||||||
@ -70,25 +75,6 @@ public class MimeBodyPart extends BodyPart {
|
|||||||
@Override
|
@Override
|
||||||
public void setBody(Body body) throws MessagingException {
|
public void setBody(Body body) throws MessagingException {
|
||||||
this.mBody = body;
|
this.mBody = body;
|
||||||
if (body instanceof Multipart) {
|
|
||||||
Multipart multipart = ((Multipart)body);
|
|
||||||
multipart.setParent(this);
|
|
||||||
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;\r\n charset=utf-8", getMimeType());
|
|
||||||
String name = MimeUtility.getHeaderParameter(getContentType(), "name");
|
|
||||||
if (name != null) {
|
|
||||||
contentType += String.format(";\r\n name=\"%s\"", name);
|
|
||||||
}
|
|
||||||
setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
|
|
||||||
setEncoding(MimeUtil.ENC_8BIT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -52,7 +52,13 @@ public class MimeHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addHeader(String name, String value) {
|
public void addHeader(String name, String value) {
|
||||||
mFields.add(new Field(name, MimeUtility.foldAndEncode(value)));
|
Field field = Field.newNameValueField(name, MimeUtility.foldAndEncode(value));
|
||||||
|
mFields.add(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addRawHeader(String name, String raw) {
|
||||||
|
Field field = Field.newRawField(name, raw);
|
||||||
|
mFields.add(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHeader(String name, String value) {
|
public void setHeader(String name, String value) {
|
||||||
@ -66,7 +72,7 @@ public class MimeHeader {
|
|||||||
public Set<String> getHeaderNames() {
|
public Set<String> getHeaderNames() {
|
||||||
Set<String> names = new LinkedHashSet<String>();
|
Set<String> names = new LinkedHashSet<String>();
|
||||||
for (Field field : mFields) {
|
for (Field field : mFields) {
|
||||||
names.add(field.name);
|
names.add(field.getName());
|
||||||
}
|
}
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
@ -74,8 +80,8 @@ public class MimeHeader {
|
|||||||
public String[] getHeader(String name) {
|
public String[] getHeader(String name) {
|
||||||
List<String> values = new ArrayList<String>();
|
List<String> values = new ArrayList<String>();
|
||||||
for (Field field : mFields) {
|
for (Field field : mFields) {
|
||||||
if (field.name.equalsIgnoreCase(name)) {
|
if (field.getName().equalsIgnoreCase(name)) {
|
||||||
values.add(field.value);
|
values.add(field.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (values.isEmpty()) {
|
if (values.isEmpty()) {
|
||||||
@ -87,7 +93,7 @@ public class MimeHeader {
|
|||||||
public void removeHeader(String name) {
|
public void removeHeader(String name) {
|
||||||
List<Field> removeFields = new ArrayList<Field>();
|
List<Field> removeFields = new ArrayList<Field>();
|
||||||
for (Field field : mFields) {
|
for (Field field : mFields) {
|
||||||
if (field.name.equalsIgnoreCase(name)) {
|
if (field.getName().equalsIgnoreCase(name)) {
|
||||||
removeFields.add(field);
|
removeFields.add(field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,27 +103,35 @@ public class MimeHeader {
|
|||||||
public void writeTo(OutputStream out) throws IOException {
|
public void writeTo(OutputStream out) throws IOException {
|
||||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
||||||
for (Field field : mFields) {
|
for (Field field : mFields) {
|
||||||
if (!Utility.arrayContains(writeOmitFields, field.name)) {
|
if (!Utility.arrayContains(writeOmitFields, field.getName())) {
|
||||||
String v = field.value;
|
if (field.hasRawData()) {
|
||||||
|
writer.write(field.getRaw());
|
||||||
if (hasToBeEncoded(v)) {
|
} else {
|
||||||
Charset charset = null;
|
writeNameValueField(writer, field);
|
||||||
|
|
||||||
if (mCharset != null) {
|
|
||||||
charset = Charset.forName(mCharset);
|
|
||||||
}
|
|
||||||
v = EncoderUtil.encodeEncodedWord(field.value, charset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.write(field.name);
|
|
||||||
writer.write(": ");
|
|
||||||
writer.write(v);
|
|
||||||
writer.write("\r\n");
|
writer.write("\r\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writer.flush();
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeNameValueField(BufferedWriter writer, Field field) throws IOException {
|
||||||
|
String value = field.getValue();
|
||||||
|
|
||||||
|
if (hasToBeEncoded(value)) {
|
||||||
|
Charset charset = null;
|
||||||
|
|
||||||
|
if (mCharset != null) {
|
||||||
|
charset = Charset.forName(mCharset);
|
||||||
|
}
|
||||||
|
value = EncoderUtil.encodeEncodedWord(field.getValue(), charset);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write(field.getName());
|
||||||
|
writer.write(": ");
|
||||||
|
writer.write(value);
|
||||||
|
}
|
||||||
|
|
||||||
// encode non printable characters except LF/CR/TAB codes.
|
// encode non printable characters except LF/CR/TAB codes.
|
||||||
private boolean hasToBeEncoded(String text) {
|
private boolean hasToBeEncoded(String text) {
|
||||||
for (int i = 0; i < text.length(); i++) {
|
for (int i = 0; i < text.length(); i++) {
|
||||||
@ -133,19 +147,67 @@ public class MimeHeader {
|
|||||||
|
|
||||||
private static class Field {
|
private static class Field {
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
private final String raw;
|
||||||
|
|
||||||
|
public static Field newNameValueField(String name, String value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new NullPointerException("Argument 'value' cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Field(name, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Field newRawField(String name, String raw) {
|
||||||
|
if (raw == null) {
|
||||||
|
throw new NullPointerException("Argument 'raw' cannot be null");
|
||||||
|
}
|
||||||
|
if (name != null && !raw.startsWith(name + ":")) {
|
||||||
|
throw new IllegalArgumentException("The value of 'raw' needs to start with the supplied field name " +
|
||||||
|
"followed by a colon");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Field(name, null, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Field(String name, String value, String raw) {
|
||||||
|
if (name == null) {
|
||||||
|
throw new NullPointerException("Argument 'name' cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
public Field(String name, String value) {
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.raw = raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delimiterIndex = raw.indexOf(':');
|
||||||
|
if (delimiterIndex == raw.length() - 1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw.substring(delimiterIndex + 1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRaw() {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasRawData() {
|
||||||
|
return raw != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder("(");
|
return (hasRawData()) ? getRaw() : getName() + ": " + getValue();
|
||||||
sb.append(name).append('=').append(value).append(')');
|
|
||||||
return sb.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
package com.fsck.k9.mail.internet;
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@ -14,6 +15,7 @@ import java.util.Locale;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.james.mime4j.MimeException;
|
import org.apache.james.mime4j.MimeException;
|
||||||
import org.apache.james.mime4j.dom.field.DateTimeField;
|
import org.apache.james.mime4j.dom.field.DateTimeField;
|
||||||
import org.apache.james.mime4j.field.DefaultFieldParser;
|
import org.apache.james.mime4j.field.DefaultFieldParser;
|
||||||
@ -396,22 +398,6 @@ public class MimeMessage extends Message {
|
|||||||
@Override
|
@Override
|
||||||
public void setBody(Body body) throws MessagingException {
|
public void setBody(Body body) throws MessagingException {
|
||||||
this.mBody = body;
|
this.mBody = body;
|
||||||
setHeader("MIME-Version", "1.0");
|
|
||||||
if (body instanceof Multipart) {
|
|
||||||
Multipart multipart = ((Multipart)body);
|
|
||||||
multipart.setParent(this);
|
|
||||||
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;\r\n charset=utf-8",
|
|
||||||
getMimeType()));
|
|
||||||
setEncoding(MimeUtil.ENC_8BIT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFirstHeader(String name) {
|
private String getFirstHeader(String name) {
|
||||||
@ -423,6 +409,11 @@ public class MimeMessage extends Message {
|
|||||||
mHeader.addHeader(name, value);
|
mHeader.addHeader(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRawHeader(String name, String raw) {
|
||||||
|
mHeader.addRawHeader(name, raw);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setHeader(String name, String value) throws UnavailableStorageException {
|
public void setHeader(String name, String value) throws UnavailableStorageException {
|
||||||
mHeader.setHeader(name, value);
|
mHeader.setHeader(name, value);
|
||||||
@ -542,8 +533,7 @@ public class MimeMessage extends Message {
|
|||||||
public void body(BodyDescriptor bd, InputStream in) throws IOException {
|
public void body(BodyDescriptor bd, InputStream in) throws IOException {
|
||||||
expect(Part.class);
|
expect(Part.class);
|
||||||
try {
|
try {
|
||||||
Body body = MimeUtility.decodeBody(in,
|
Body body = MimeUtility.createBody(in, bd.getTransferEncoding(), bd.getMimeType());
|
||||||
bd.getTransferEncoding(), bd.getMimeType());
|
|
||||||
((Part)stack.peek()).setBody(body);
|
((Part)stack.peek()).setBody(body);
|
||||||
} catch (MessagingException me) {
|
} catch (MessagingException me) {
|
||||||
throw new Error(me);
|
throw new Error(me);
|
||||||
@ -577,16 +567,17 @@ public class MimeMessage extends Message {
|
|||||||
@Override
|
@Override
|
||||||
public void preamble(InputStream is) throws IOException {
|
public void preamble(InputStream is) throws IOException {
|
||||||
expect(MimeMultipart.class);
|
expect(MimeMultipart.class);
|
||||||
StringBuilder sb = new StringBuilder();
|
ByteArrayOutputStream preamble = new ByteArrayOutputStream();
|
||||||
int b;
|
IOUtils.copy(is, preamble);
|
||||||
while ((b = is.read()) != -1) {
|
((MimeMultipart)stack.peek()).setPreamble(preamble.toByteArray());
|
||||||
sb.append((char)b);
|
|
||||||
}
|
|
||||||
((MimeMultipart)stack.peek()).setPreamble(sb.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void epilogue(InputStream is) throws IOException {
|
public void epilogue(InputStream is) throws IOException {
|
||||||
|
expect(MimeMultipart.class);
|
||||||
|
ByteArrayOutputStream epilogue = new ByteArrayOutputStream();
|
||||||
|
IOUtils.copy(is, epilogue);
|
||||||
|
((MimeMultipart) stack.peek()).setEpilogue(epilogue.toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -598,7 +589,9 @@ public class MimeMessage extends Message {
|
|||||||
public void field(Field parsedField) throws MimeException {
|
public void field(Field parsedField) throws MimeException {
|
||||||
expect(Part.class);
|
expect(Part.class);
|
||||||
try {
|
try {
|
||||||
((Part)stack.peek()).addHeader(parsedField.getName(), parsedField.getBody().trim());
|
String name = parsedField.getName();
|
||||||
|
String raw = parsedField.getRaw().toString();
|
||||||
|
((Part) stack.peek()).addRawHeader(name, raw);
|
||||||
} catch (MessagingException me) {
|
} catch (MessagingException me) {
|
||||||
throw new Error(me);
|
throw new Error(me);
|
||||||
}
|
}
|
||||||
|
52
src/com/fsck/k9/mail/internet/MimeMessageHelper.java
Normal file
52
src/com/fsck/k9/mail/internet/MimeMessageHelper.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.Body;
|
||||||
|
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 org.apache.james.mime4j.util.MimeUtil;
|
||||||
|
|
||||||
|
|
||||||
|
public class MimeMessageHelper {
|
||||||
|
private MimeMessageHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setBody(Part part, Body body) throws MessagingException {
|
||||||
|
part.setBody(body);
|
||||||
|
|
||||||
|
if (part instanceof Message) {
|
||||||
|
part.setHeader("MIME-Version", "1.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body instanceof Multipart) {
|
||||||
|
Multipart multipart = ((Multipart) body);
|
||||||
|
multipart.setParent(part);
|
||||||
|
String type = multipart.getContentType();
|
||||||
|
part.setHeader(MimeHeader.HEADER_CONTENT_TYPE, type);
|
||||||
|
if ("multipart/signed".equalsIgnoreCase(type)) {
|
||||||
|
setEncoding(part, MimeUtil.ENC_7BIT);
|
||||||
|
} else {
|
||||||
|
setEncoding(part, MimeUtil.ENC_8BIT);
|
||||||
|
}
|
||||||
|
} else if (body instanceof TextBody) {
|
||||||
|
String contentType = String.format("%s;\r\n charset=utf-8", part.getMimeType());
|
||||||
|
String name = MimeUtility.getHeaderParameter(part.getContentType(), "name");
|
||||||
|
if (name != null) {
|
||||||
|
contentType += String.format(";\r\n name=\"%s\"", name);
|
||||||
|
}
|
||||||
|
part.setHeader(MimeHeader.HEADER_CONTENT_TYPE, contentType);
|
||||||
|
|
||||||
|
setEncoding(part, MimeUtil.ENC_8BIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setEncoding(Part part, String encoding) throws MessagingException {
|
||||||
|
Body body = part.getBody();
|
||||||
|
if (body != null) {
|
||||||
|
body.setEncoding(encoding);
|
||||||
|
}
|
||||||
|
part.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,8 @@ import java.util.Locale;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class MimeMultipart extends Multipart {
|
public class MimeMultipart extends Multipart {
|
||||||
private String mPreamble;
|
private byte[] mPreamble;
|
||||||
|
private byte[] mEpilogue;
|
||||||
|
|
||||||
private String mContentType;
|
private String mContentType;
|
||||||
|
|
||||||
@ -45,12 +46,12 @@ public class MimeMultipart extends Multipart {
|
|||||||
return sb.toString().toUpperCase(Locale.US);
|
return sb.toString().toUpperCase(Locale.US);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPreamble() {
|
public void setPreamble(byte[] preamble) {
|
||||||
return mPreamble;
|
this.mPreamble = preamble;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPreamble(String preamble) {
|
public void setEpilogue(byte[] epilogue) {
|
||||||
this.mPreamble = preamble;
|
mEpilogue = epilogue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -67,7 +68,7 @@ public class MimeMultipart extends Multipart {
|
|||||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
|
||||||
|
|
||||||
if (mPreamble != null) {
|
if (mPreamble != null) {
|
||||||
writer.write(mPreamble);
|
out.write(mPreamble);
|
||||||
writer.write("\r\n");
|
writer.write("\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +91,9 @@ public class MimeMultipart extends Multipart {
|
|||||||
writer.write(mBoundary);
|
writer.write(mBoundary);
|
||||||
writer.write("--\r\n");
|
writer.write("--\r\n");
|
||||||
writer.flush();
|
writer.flush();
|
||||||
|
if (mEpilogue != null) {
|
||||||
|
out.write(mEpilogue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
package com.fsck.k9.mail.internet;
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
@ -10,6 +11,7 @@ import com.fsck.k9.mail.*;
|
|||||||
import com.fsck.k9.mail.Message.RecipientType;
|
import com.fsck.k9.mail.Message.RecipientType;
|
||||||
import com.fsck.k9.mail.internet.BinaryTempFileBody.BinaryTempFileBodyInputStream;
|
import com.fsck.k9.mail.internet.BinaryTempFileBody.BinaryTempFileBodyInputStream;
|
||||||
|
|
||||||
|
import com.fsck.k9.view.MessageHeader;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.james.mime4j.codec.Base64InputStream;
|
import org.apache.james.mime4j.codec.Base64InputStream;
|
||||||
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
|
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
|
||||||
@ -1026,7 +1028,7 @@ public class MimeUtility {
|
|||||||
* determine the charset from HTML message.
|
* determine the charset from HTML message.
|
||||||
*/
|
*/
|
||||||
if (mimeType.equalsIgnoreCase("text/html") && charset == null) {
|
if (mimeType.equalsIgnoreCase("text/html") && charset == null) {
|
||||||
InputStream in = part.getBody().getInputStream();
|
InputStream in = MimeUtility.decodeBody(part.getBody());
|
||||||
try {
|
try {
|
||||||
byte[] buf = new byte[256];
|
byte[] buf = new byte[256];
|
||||||
in.read(buf, 0, buf.length);
|
in.read(buf, 0, buf.length);
|
||||||
@ -1062,12 +1064,12 @@ public class MimeUtility {
|
|||||||
* Now we read the part into a buffer for further processing. Because
|
* 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.
|
* the stream is now wrapped we'll remove any transfer encoding at this point.
|
||||||
*/
|
*/
|
||||||
InputStream in = part.getBody().getInputStream();
|
InputStream in = MimeUtility.decodeBody(part.getBody());
|
||||||
try {
|
try {
|
||||||
String text = readToString(in, charset);
|
String text = readToString(in, charset);
|
||||||
|
|
||||||
// Replace the body with a TextBody that already contains the decoded text
|
// Replace the body with a TextBody that already contains the decoded text
|
||||||
part.setBody(new TextBody(text));
|
MimeMessageHelper.setBody(part, new TextBody(text));
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
} finally {
|
} finally {
|
||||||
@ -1115,42 +1117,73 @@ public class MimeUtility {
|
|||||||
return DEFAULT_ATTACHMENT_MIME_TYPE.equalsIgnoreCase(mimeType);
|
return DEFAULT_ATTACHMENT_MIME_TYPE.equalsIgnoreCase(mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static Body createBody(InputStream in, String contentTransferEncoding, String contentType)
|
||||||
* Removes any content transfer encoding from the stream and returns a Body.
|
|
||||||
* @throws MessagingException
|
|
||||||
*/
|
|
||||||
public static Body decodeBody(InputStream in,
|
|
||||||
String contentTransferEncoding, String contentType)
|
|
||||||
throws IOException, MessagingException {
|
throws IOException, MessagingException {
|
||||||
/*
|
|
||||||
* We'll remove any transfer encoding by wrapping the stream.
|
|
||||||
*/
|
|
||||||
if (contentTransferEncoding != null) {
|
if (contentTransferEncoding != null) {
|
||||||
contentTransferEncoding =
|
contentTransferEncoding = MimeUtility.getHeaderParameter(contentTransferEncoding, null);
|
||||||
MimeUtility.getHeaderParameter(contentTransferEncoding, null);
|
|
||||||
if (MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(contentTransferEncoding)) {
|
|
||||||
in = new QuotedPrintableInputStream(in);
|
|
||||||
} else if (MimeUtil.ENC_BASE64.equalsIgnoreCase(contentTransferEncoding)) {
|
|
||||||
in = new Base64InputStream(in);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BinaryTempFileBody tempBody;
|
BinaryTempFileBody tempBody;
|
||||||
if (MimeUtil.isMessage(contentType)) {
|
if (MimeUtil.isMessage(contentType)) {
|
||||||
tempBody = new BinaryTempFileMessageBody();
|
tempBody = new BinaryTempFileMessageBody(contentTransferEncoding);
|
||||||
} else {
|
} else {
|
||||||
tempBody = new BinaryTempFileBody();
|
tempBody = new BinaryTempFileBody(contentTransferEncoding);
|
||||||
}
|
}
|
||||||
tempBody.setEncoding(contentTransferEncoding);
|
|
||||||
OutputStream out = tempBody.getOutputStream();
|
OutputStream out = tempBody.getOutputStream();
|
||||||
try {
|
try {
|
||||||
IOUtils.copy(in, out);
|
IOUtils.copy(in, out);
|
||||||
} finally {
|
} finally {
|
||||||
out.close();
|
out.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return tempBody;
|
return tempBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get decoded contents of a body.
|
||||||
|
* <p/>
|
||||||
|
* Right now only some classes retain the original encoding of the body contents. Those classes have to implement
|
||||||
|
* the {@link RawDataBody} interface in order for this method to decode the data delivered by
|
||||||
|
* {@link Body#getInputStream()}.
|
||||||
|
* <p/>
|
||||||
|
* The ultimate goal is to get to a point where all classes retain the original data and {@code RawDataBody} can be
|
||||||
|
* merged into {@link Body}.
|
||||||
|
*/
|
||||||
|
public static InputStream decodeBody(Body body) throws MessagingException {
|
||||||
|
InputStream inputStream;
|
||||||
|
if (body instanceof RawDataBody) {
|
||||||
|
RawDataBody rawDataBody = (RawDataBody) body;
|
||||||
|
String encoding = rawDataBody.getEncoding();
|
||||||
|
final InputStream rawInputStream = rawDataBody.getInputStream();
|
||||||
|
if (MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding) || MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
|
||||||
|
inputStream = rawInputStream;
|
||||||
|
} else if (MimeUtil.ENC_BASE64.equalsIgnoreCase(encoding)) {
|
||||||
|
inputStream = new Base64InputStream(rawInputStream, false) {
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
rawInputStream.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if (MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(encoding)) {
|
||||||
|
inputStream = new QuotedPrintableInputStream(rawInputStream) {
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
rawInputStream.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Encoding for RawDataBody not supported: " + encoding);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inputStream = body.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty base class for the class hierarchy used by
|
* Empty base class for the class hierarchy used by
|
||||||
|
12
src/com/fsck/k9/mail/internet/RawDataBody.java
Normal file
12
src/com/fsck/k9/mail/internet/RawDataBody.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package com.fsck.k9.mail.internet;
|
||||||
|
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.Body;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link MimeUtility#decodeBody(Body)}
|
||||||
|
*/
|
||||||
|
public interface RawDataBody extends Body {
|
||||||
|
String getEncoding();
|
||||||
|
}
|
@ -26,8 +26,6 @@ import java.security.Security;
|
|||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
@ -49,6 +47,7 @@ import java.util.regex.Pattern;
|
|||||||
import java.util.zip.Inflater;
|
import java.util.zip.Inflater;
|
||||||
import java.util.zip.InflaterInputStream;
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.internet.MimeMessageHelper;
|
||||||
import javax.net.ssl.SSLException;
|
import javax.net.ssl.SSLException;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
@ -1616,7 +1615,7 @@ public class ImapStore extends Store {
|
|||||||
if (literal != null) {
|
if (literal != null) {
|
||||||
if (literal instanceof Body) {
|
if (literal instanceof Body) {
|
||||||
// Most of the work was done in FetchAttchmentCallback.foundLiteral()
|
// Most of the work was done in FetchAttchmentCallback.foundLiteral()
|
||||||
part.setBody((Body)literal);
|
MimeMessageHelper.setBody(part, (Body) literal);
|
||||||
} else if (literal instanceof String) {
|
} else if (literal instanceof String) {
|
||||||
String bodyString = (String)literal;
|
String bodyString = (String)literal;
|
||||||
InputStream bodyStream = new ByteArrayInputStream(bodyString.getBytes());
|
InputStream bodyStream = new ByteArrayInputStream(bodyString.getBytes());
|
||||||
@ -1625,7 +1624,7 @@ public class ImapStore extends Store {
|
|||||||
.getHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)[0];
|
.getHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING)[0];
|
||||||
String contentType = part
|
String contentType = part
|
||||||
.getHeader(MimeHeader.HEADER_CONTENT_TYPE)[0];
|
.getHeader(MimeHeader.HEADER_CONTENT_TYPE)[0];
|
||||||
part.setBody(MimeUtility.decodeBody(bodyStream,
|
MimeMessageHelper.setBody(part, MimeUtility.createBody(bodyStream,
|
||||||
contentTransferEncoding, contentType));
|
contentTransferEncoding, contentType));
|
||||||
} else {
|
} else {
|
||||||
// This shouldn't happen
|
// This shouldn't happen
|
||||||
@ -3598,7 +3597,7 @@ public class ImapStore extends Store {
|
|||||||
String contentType = mPart
|
String contentType = mPart
|
||||||
.getHeader(MimeHeader.HEADER_CONTENT_TYPE)[0];
|
.getHeader(MimeHeader.HEADER_CONTENT_TYPE)[0];
|
||||||
|
|
||||||
return MimeUtility.decodeBody(literal, contentTransferEncoding,
|
return MimeUtility.createBody(literal, contentTransferEncoding,
|
||||||
contentType);
|
contentType);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -19,6 +19,7 @@ import java.util.Set;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.internet.MimeMessageHelper;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.james.mime4j.util.MimeUtil;
|
import org.apache.james.mime4j.util.MimeUtil;
|
||||||
|
|
||||||
@ -774,17 +775,17 @@ public class LocalFolder extends Folder implements Serializable {
|
|||||||
// triggering T_MIME_NO_TEXT and T_TVD_MIME_NO_HEADERS
|
// triggering T_MIME_NO_TEXT and T_TVD_MIME_NO_HEADERS
|
||||||
// SpamAssassin rules.
|
// SpamAssassin rules.
|
||||||
localMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "text/plain");
|
localMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "text/plain");
|
||||||
localMessage.setBody(new TextBody(""));
|
MimeMessageHelper.setBody(localMessage, new TextBody(""));
|
||||||
} else if (mp.getCount() == 1 && (mp.getBodyPart(0) instanceof LocalAttachmentBodyPart) == false)
|
} else if (mp.getCount() == 1 && (mp.getBodyPart(0) instanceof LocalAttachmentBodyPart) == false)
|
||||||
|
|
||||||
{
|
{
|
||||||
// If we have only one part, drop the MimeMultipart container.
|
// If we have only one part, drop the MimeMultipart container.
|
||||||
BodyPart part = mp.getBodyPart(0);
|
BodyPart part = mp.getBodyPart(0);
|
||||||
localMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, part.getContentType());
|
localMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, part.getContentType());
|
||||||
localMessage.setBody(part.getBody());
|
MimeMessageHelper.setBody(localMessage, part.getBody());
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, attach the MimeMultipart to the message.
|
// Otherwise, attach the MimeMultipart to the message.
|
||||||
localMessage.setBody(mp);
|
MimeMessageHelper.setBody(localMessage, mp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1560,7 +1561,7 @@ public class LocalFolder extends Folder implements Serializable {
|
|||||||
* If the attachment has a body we're expected to save it into the local store
|
* If the attachment has a body we're expected to save it into the local store
|
||||||
* so we copy the data into a cached attachment file.
|
* so we copy the data into a cached attachment file.
|
||||||
*/
|
*/
|
||||||
InputStream in = attachment.getBody().getInputStream();
|
InputStream in = MimeUtility.decodeBody(attachment.getBody());
|
||||||
try {
|
try {
|
||||||
tempAttachmentFile = File.createTempFile("att", null, attachmentDirectory);
|
tempAttachmentFile = File.createTempFile("att", null, attachmentDirectory);
|
||||||
FileOutputStream out = new FileOutputStream(tempAttachmentFile);
|
FileOutputStream out = new FileOutputStream(tempAttachmentFile);
|
||||||
@ -1642,11 +1643,13 @@ public class LocalFolder extends Folder implements Serializable {
|
|||||||
mAccount,
|
mAccount,
|
||||||
attachmentId);
|
attachmentId);
|
||||||
if (MimeUtil.isMessage(attachment.getMimeType())) {
|
if (MimeUtil.isMessage(attachment.getMimeType())) {
|
||||||
attachment.setBody(new LocalAttachmentMessageBody(
|
LocalAttachmentMessageBody body = new LocalAttachmentMessageBody(
|
||||||
contentUri, LocalFolder.this.localStore.mApplication));
|
contentUri, LocalFolder.this.localStore.mApplication);
|
||||||
|
MimeMessageHelper.setBody(attachment, body);
|
||||||
} else {
|
} else {
|
||||||
attachment.setBody(new LocalAttachmentBody(
|
LocalAttachmentBody body = new LocalAttachmentBody(
|
||||||
contentUri, LocalFolder.this.localStore.mApplication));
|
contentUri, LocalFolder.this.localStore.mApplication);
|
||||||
|
MimeMessageHelper.setBody(attachment, body);
|
||||||
}
|
}
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
|
cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
|
||||||
|
@ -507,6 +507,11 @@ public class LocalMessage extends MimeMessage {
|
|||||||
super.addHeader(name, value);
|
super.addHeader(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addRawHeader(String name, String raw) {
|
||||||
|
throw new RuntimeException("Not supported");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setHeader(String name, String value) throws UnavailableStorageException {
|
public void setHeader(String name, String value) throws UnavailableStorageException {
|
||||||
if (!mHeadersLoaded)
|
if (!mHeadersLoaded)
|
||||||
|
@ -5,6 +5,8 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.internet.MimeMessageHelper;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.james.mime4j.codec.Base64InputStream;
|
import org.apache.james.mime4j.codec.Base64InputStream;
|
||||||
import org.apache.james.mime4j.util.MimeUtil;
|
import org.apache.james.mime4j.util.MimeUtil;
|
||||||
@ -191,7 +193,8 @@ public class MessageTest extends AndroidTestCase {
|
|||||||
+ "Content-Transfer-Encoding: 7bit\r\n"
|
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "------Boundary102\r\n"
|
+ "------Boundary102\r\n"
|
||||||
+ "Content-Type: text/plain; charset=utf-8\r\n"
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "Testing=2E\r\n"
|
+ "Testing=2E\r\n"
|
||||||
@ -200,7 +203,8 @@ public class MessageTest extends AndroidTestCase {
|
|||||||
+ "End of test=2E\r\n"
|
+ "End of test=2E\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "------Boundary102\r\n"
|
+ "------Boundary102\r\n"
|
||||||
+ "Content-Type: text/plain; charset=utf-8\r\n"
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "Testing=2E\r\n"
|
+ "Testing=2E\r\n"
|
||||||
@ -228,7 +232,8 @@ public class MessageTest extends AndroidTestCase {
|
|||||||
+ "Content-Transfer-Encoding: 7bit\r\n"
|
+ "Content-Transfer-Encoding: 7bit\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "------Boundary101\r\n"
|
+ "------Boundary101\r\n"
|
||||||
+ "Content-Type: text/plain; charset=utf-8\r\n"
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "Testing=2E\r\n"
|
+ "Testing=2E\r\n"
|
||||||
@ -237,7 +242,8 @@ public class MessageTest extends AndroidTestCase {
|
|||||||
+ "End of test=2E\r\n"
|
+ "End of test=2E\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "------Boundary101\r\n"
|
+ "------Boundary101\r\n"
|
||||||
+ "Content-Type: text/plain; charset=utf-8\r\n"
|
+ "Content-Type: text/plain;\r\n"
|
||||||
|
+ " charset=utf-8\r\n"
|
||||||
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
+ "Content-Transfer-Encoding: quoted-printable\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "Testing=2E\r\n"
|
+ "Testing=2E\r\n"
|
||||||
@ -285,8 +291,7 @@ public class MessageTest extends AndroidTestCase {
|
|||||||
|
|
||||||
private MimeMessage nestedMessage(MimeMessage subMessage)
|
private MimeMessage nestedMessage(MimeMessage subMessage)
|
||||||
throws MessagingException, IOException {
|
throws MessagingException, IOException {
|
||||||
BinaryTempFileMessageBody tempMessageBody = new BinaryTempFileMessageBody();
|
BinaryTempFileMessageBody tempMessageBody = new BinaryTempFileMessageBody(MimeUtil.ENC_8BIT);
|
||||||
tempMessageBody.setEncoding(MimeUtil.ENC_8BIT);
|
|
||||||
|
|
||||||
OutputStream out = tempMessageBody.getOutputStream();
|
OutputStream out = tempMessageBody.getOutputStream();
|
||||||
try {
|
try {
|
||||||
@ -318,7 +323,7 @@ public class MessageTest extends AndroidTestCase {
|
|||||||
multipartBody.addBodyPart(textBodyPart(MimeUtil.ENC_8BIT));
|
multipartBody.addBodyPart(textBodyPart(MimeUtil.ENC_8BIT));
|
||||||
multipartBody.addBodyPart(textBodyPart(MimeUtil.ENC_QUOTED_PRINTABLE));
|
multipartBody.addBodyPart(textBodyPart(MimeUtil.ENC_QUOTED_PRINTABLE));
|
||||||
multipartBody.addBodyPart(binaryBodyPart());
|
multipartBody.addBodyPart(binaryBodyPart());
|
||||||
message.setBody(multipartBody);
|
MimeMessageHelper.setBody(message, multipartBody);
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
@ -326,12 +331,12 @@ public class MessageTest extends AndroidTestCase {
|
|||||||
private MimeBodyPart binaryBodyPart() throws IOException,
|
private MimeBodyPart binaryBodyPart() throws IOException,
|
||||||
MessagingException {
|
MessagingException {
|
||||||
String encodedTestString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
String encodedTestString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
+ "abcdefghijklmnopqrstuvwxyz0123456789+/";
|
+ "abcdefghijklmnopqrstuvwxyz0123456789+/\r\n";
|
||||||
|
|
||||||
BinaryTempFileBody tempFileBody = new BinaryTempFileBody();
|
BinaryTempFileBody tempFileBody = new BinaryTempFileBody(MimeUtil.ENC_BASE64);
|
||||||
|
|
||||||
InputStream in = new Base64InputStream(new ByteArrayInputStream(
|
InputStream in = new ByteArrayInputStream(
|
||||||
encodedTestString.getBytes("UTF-8")));
|
encodedTestString.getBytes("UTF-8"));
|
||||||
|
|
||||||
OutputStream out = tempFileBody.getOutputStream();
|
OutputStream out = tempFileBody.getOutputStream();
|
||||||
try {
|
try {
|
||||||
|
69
tests/src/com/fsck/k9/mail/ReconstructMessageTest.java
Normal file
69
tests/src/com/fsck/k9/mail/ReconstructMessageTest.java
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package com.fsck.k9.mail;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import android.test.AndroidTestCase;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.internet.BinaryTempFileBody;
|
||||||
|
import com.fsck.k9.mail.internet.MimeMessage;
|
||||||
|
|
||||||
|
|
||||||
|
public class ReconstructMessageTest extends AndroidTestCase {
|
||||||
|
|
||||||
|
public void testMessage() throws IOException, MessagingException {
|
||||||
|
String messageSource =
|
||||||
|
"From: from@example.com\r\n" +
|
||||||
|
"To: to@example.com\r\n" +
|
||||||
|
"Subject: Test Message \r\n" +
|
||||||
|
"Date: Thu, 13 Nov 2014 17:09:38 +0100\r\n" +
|
||||||
|
"Content-Type: multipart/mixed;\r\n" +
|
||||||
|
" boundary=\"----Boundary\"\r\n" +
|
||||||
|
"Content-Transfer-Encoding: 8bit\r\n" +
|
||||||
|
"MIME-Version: 1.0\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"This is a multipart MIME message.\r\n" +
|
||||||
|
"------Boundary\r\n" +
|
||||||
|
"Content-Type: text/plain; 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" +
|
||||||
|
"------Boundary\r\n" +
|
||||||
|
"Content-Type: text/plain\r\n" +
|
||||||
|
"Content-Transfer-Encoding: base64\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"VGhpcyBpcyBhIHRl\r\n" +
|
||||||
|
"c3QgbWVzc2FnZQ==\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"------Boundary--\r\n" +
|
||||||
|
"Hi, I'm the epilogue";
|
||||||
|
|
||||||
|
BinaryTempFileBody.setTempDirectory(getContext().getCacheDir());
|
||||||
|
|
||||||
|
InputStream messageInputStream = new ByteArrayInputStream(messageSource.getBytes());
|
||||||
|
MimeMessage message;
|
||||||
|
try {
|
||||||
|
message = new MimeMessage(messageInputStream, true);
|
||||||
|
} finally {
|
||||||
|
messageInputStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream messageOutputStream = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
message.writeTo(messageOutputStream);
|
||||||
|
} finally {
|
||||||
|
messageOutputStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
String reconstructedMessage = new String(messageOutputStream.toByteArray());
|
||||||
|
|
||||||
|
assertEquals(messageSource, reconstructedMessage);
|
||||||
|
}
|
||||||
|
}
|
@ -63,7 +63,7 @@ public class MimeMessageParseTest extends AndroidTestCase {
|
|||||||
private static void checkLeafParts(MimeMessage msg, String... expectedParts) throws Exception {
|
private static void checkLeafParts(MimeMessage msg, String... expectedParts) throws Exception {
|
||||||
List<String> actual = new ArrayList<String>();
|
List<String> actual = new ArrayList<String>();
|
||||||
for (Body leaf : getLeafParts(msg.getBody())) {
|
for (Body leaf : getLeafParts(msg.getBody())) {
|
||||||
actual.add(streamToString(leaf.getInputStream()));
|
actual.add(streamToString(MimeUtility.decodeBody(leaf)));
|
||||||
}
|
}
|
||||||
assertEquals(Arrays.asList(expectedParts), actual);
|
assertEquals(Arrays.asList(expectedParts), actual);
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@ public class MimeMessageParseTest extends AndroidTestCase {
|
|||||||
checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org");
|
checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org");
|
||||||
assertEquals("Testmail", msg.getSubject());
|
assertEquals("Testmail", msg.getSubject());
|
||||||
assertEquals("text/plain", msg.getContentType());
|
assertEquals("text/plain", msg.getContentType());
|
||||||
assertEquals("this is some test text.", streamToString(msg.getBody().getInputStream()));
|
assertEquals("this is some test text.", streamToString(MimeUtility.decodeBody(msg.getBody())));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void testSinglePart8BitRecurse() throws Exception {
|
public static void testSinglePart8BitRecurse() throws Exception {
|
||||||
@ -101,7 +101,7 @@ public class MimeMessageParseTest extends AndroidTestCase {
|
|||||||
checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org");
|
checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org");
|
||||||
assertEquals("Testmail", msg.getSubject());
|
assertEquals("Testmail", msg.getSubject());
|
||||||
assertEquals("text/plain; encoding=ISO-8859-1", msg.getContentType());
|
assertEquals("text/plain; encoding=ISO-8859-1", msg.getContentType());
|
||||||
assertEquals("gefährliche Umlaute", streamToString(msg.getBody().getInputStream()));
|
assertEquals("gefährliche Umlaute", streamToString(MimeUtility.decodeBody(msg.getBody())));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void testSinglePartBase64NoRecurse() throws Exception {
|
public static void testSinglePartBase64NoRecurse() throws Exception {
|
||||||
@ -119,7 +119,7 @@ public class MimeMessageParseTest extends AndroidTestCase {
|
|||||||
checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org");
|
checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org");
|
||||||
assertEquals("Testmail", msg.getSubject());
|
assertEquals("Testmail", msg.getSubject());
|
||||||
assertEquals("text/plain", msg.getContentType());
|
assertEquals("text/plain", msg.getContentType());
|
||||||
assertEquals("this is some more test text.", streamToString(msg.getBody().getInputStream()));
|
assertEquals("this is some more test text.", streamToString(MimeUtility.decodeBody(msg.getBody())));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void testMultipartSingleLayerNoRecurse() throws Exception {
|
public static void testMultipartSingleLayerNoRecurse() throws Exception {
|
||||||
|
@ -21,7 +21,7 @@ public class ViewablesTest extends AndroidTestCase {
|
|||||||
|
|
||||||
// Create message
|
// Create message
|
||||||
MimeMessage message = new MimeMessage();
|
MimeMessage message = new MimeMessage();
|
||||||
message.setBody(body);
|
MimeMessageHelper.setBody(message, body);
|
||||||
|
|
||||||
// Extract text
|
// Extract text
|
||||||
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
|
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
|
||||||
@ -45,7 +45,7 @@ public class ViewablesTest extends AndroidTestCase {
|
|||||||
// Create message
|
// Create message
|
||||||
MimeMessage message = new MimeMessage();
|
MimeMessage message = new MimeMessage();
|
||||||
message.setHeader("Content-Type", "text/html");
|
message.setHeader("Content-Type", "text/html");
|
||||||
message.setBody(body);
|
MimeMessageHelper.setBody(message, body);
|
||||||
|
|
||||||
// Extract text
|
// Extract text
|
||||||
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
|
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
|
||||||
@ -75,7 +75,7 @@ public class ViewablesTest extends AndroidTestCase {
|
|||||||
|
|
||||||
// Create message
|
// Create message
|
||||||
MimeMessage message = new MimeMessage();
|
MimeMessage message = new MimeMessage();
|
||||||
message.setBody(multipart);
|
MimeMessageHelper.setBody(message, multipart);
|
||||||
|
|
||||||
// Extract text
|
// Extract text
|
||||||
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
|
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
|
||||||
@ -119,7 +119,7 @@ public class ViewablesTest extends AndroidTestCase {
|
|||||||
innerMessage.setRecipients(RecipientType.TO, new Address[] { new Address("to@example.com") });
|
innerMessage.setRecipients(RecipientType.TO, new Address[] { new Address("to@example.com") });
|
||||||
innerMessage.setSubject("Subject");
|
innerMessage.setSubject("Subject");
|
||||||
innerMessage.setFrom(new Address("from@example.com"));
|
innerMessage.setFrom(new Address("from@example.com"));
|
||||||
innerMessage.setBody(innerBody);
|
MimeMessageHelper.setBody(innerMessage, innerBody);
|
||||||
|
|
||||||
// Create multipart/mixed part
|
// Create multipart/mixed part
|
||||||
MimeMultipart multipart = new MimeMultipart();
|
MimeMultipart multipart = new MimeMultipart();
|
||||||
@ -131,7 +131,7 @@ public class ViewablesTest extends AndroidTestCase {
|
|||||||
|
|
||||||
// Create message
|
// Create message
|
||||||
MimeMessage message = new MimeMessage();
|
MimeMessage message = new MimeMessage();
|
||||||
message.setBody(multipart);
|
MimeMessageHelper.setBody(message, multipart);
|
||||||
|
|
||||||
// Extract text
|
// Extract text
|
||||||
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
|
ViewableContainer container = MimeUtility.extractTextAndAttachments(getContext(), message);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user