mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-27 03:32:16 -05:00
Merge pull request #524 from k9mail/merge_pgp_mime_branch
Merge changes from PGP/MIME repository
This commit is contained in:
commit
82736f3a8b
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,4 +1,4 @@
|
|||||||
#Sun Nov 30 16:02:23 PST 2014
|
#Sun Dec 07 14:12:42 GMT 2014
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
@ -14,8 +14,8 @@ import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
|
|||||||
|
|
||||||
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
|
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
|
||||||
|
|
||||||
|
|
||||||
public abstract class Message implements Part, CompositeBody {
|
public abstract class Message implements Part, CompositeBody {
|
||||||
|
|
||||||
public enum RecipientType {
|
public enum RecipientType {
|
||||||
TO, CC, BCC,
|
TO, CC, BCC,
|
||||||
}
|
}
|
||||||
@ -117,8 +117,32 @@ public abstract class Message implements Part, CompositeBody {
|
|||||||
|
|
||||||
public abstract void setReferences(String references) throws MessagingException;
|
public abstract void setReferences(String references) throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract Body getBody();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String getContentType() throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void addHeader(String name, String value) throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void addRawHeader(String name, String raw) throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void setHeader(String name, String value) throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract String[] getHeader(String name) throws MessagingException;
|
||||||
|
|
||||||
public abstract Set<String> getHeaderNames() throws MessagingException;
|
public abstract Set<String> getHeaderNames() throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void removeHeader(String name) throws MessagingException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void setBody(Body body) throws MessagingException;
|
||||||
|
|
||||||
public abstract long getId();
|
public abstract long getId();
|
||||||
|
|
||||||
public abstract String getPreview();
|
public abstract String getPreview();
|
||||||
|
@ -7,6 +7,8 @@ import java.io.OutputStream;
|
|||||||
public interface Part {
|
public interface Part {
|
||||||
void addHeader(String name, String value) throws MessagingException;
|
void addHeader(String name, String value) throws MessagingException;
|
||||||
|
|
||||||
|
void addRawHeader(String name, String raw) throws MessagingException;
|
||||||
|
|
||||||
void removeHeader(String name) throws MessagingException;
|
void removeHeader(String name) throws MessagingException;
|
||||||
|
|
||||||
void setHeader(String name, String value) throws MessagingException;
|
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 {
|
||||||
@ -53,23 +94,8 @@ 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 {
|
|
||||||
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 {
|
try {
|
||||||
IOUtils.copy(in, out);
|
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
|
||||||
|
@ -50,7 +50,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) {
|
||||||
@ -64,7 +70,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;
|
||||||
}
|
}
|
||||||
@ -72,8 +78,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()) {
|
||||||
@ -85,7 +91,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,24 +102,32 @@ public class MimeHeader {
|
|||||||
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 (!Arrays.asList(writeOmitFields).contains(field.name)) {
|
if (!Arrays.asList(writeOmitFields).contains(field.name)) {
|
||||||
String v = field.value;
|
if (field.hasRawData()) {
|
||||||
|
writer.write(field.getRaw());
|
||||||
|
} else {
|
||||||
|
writeNameValueField(writer, field);
|
||||||
|
}
|
||||||
|
writer.write("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
|
||||||
if (hasToBeEncoded(v)) {
|
private void writeNameValueField(BufferedWriter writer, Field field) throws IOException {
|
||||||
|
String value = field.getValue();
|
||||||
|
|
||||||
|
if (hasToBeEncoded(value)) {
|
||||||
Charset charset = null;
|
Charset charset = null;
|
||||||
|
|
||||||
if (mCharset != null) {
|
if (mCharset != null) {
|
||||||
charset = Charset.forName(mCharset);
|
charset = Charset.forName(mCharset);
|
||||||
}
|
}
|
||||||
v = EncoderUtil.encodeEncodedWord(field.value, charset);
|
value = EncoderUtil.encodeEncodedWord(field.getValue(), charset);
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.write(field.name);
|
writer.write(field.getName());
|
||||||
writer.write(": ");
|
writer.write(": ");
|
||||||
writer.write(v);
|
writer.write(value);
|
||||||
writer.write("\r\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writer.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// encode non printable characters except LF/CR/TAB codes.
|
// encode non printable characters except LF/CR/TAB codes.
|
||||||
@ -131,19 +145,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;
|
||||||
@ -394,22 +396,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) {
|
||||||
@ -421,6 +407,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 MessagingException {
|
public void setHeader(String name, String value) throws MessagingException {
|
||||||
mHeader.setHeader(name, value);
|
mHeader.setHeader(name, value);
|
||||||
@ -540,8 +531,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);
|
||||||
@ -575,16 +565,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
|
||||||
@ -596,7 +587,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);
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -982,42 +982,74 @@ 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);
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getMimeTypeByExtension(String filename) {
|
public static String getMimeTypeByExtension(String filename) {
|
||||||
String returnedType = null;
|
String returnedType = null;
|
||||||
String extension = null;
|
String extension = null;
|
||||||
|
@ -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();
|
||||||
|
}
|
@ -37,6 +37,7 @@ import android.os.PowerManager;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.internet.MimeMessageHelper;
|
||||||
import com.fsck.k9.mail.power.TracingPowerManager;
|
import com.fsck.k9.mail.power.TracingPowerManager;
|
||||||
import com.fsck.k9.mail.power.TracingPowerManager.TracingWakeLock;
|
import com.fsck.k9.mail.power.TracingPowerManager.TracingWakeLock;
|
||||||
import com.fsck.k9.mail.AuthType;
|
import com.fsck.k9.mail.AuthType;
|
||||||
@ -1504,7 +1505,7 @@ public class ImapStore extends RemoteStore {
|
|||||||
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());
|
||||||
@ -1513,7 +1514,7 @@ public class ImapStore extends RemoteStore {
|
|||||||
.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
|
||||||
@ -2881,7 +2882,7 @@ public class ImapStore extends RemoteStore {
|
|||||||
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;
|
||||||
|
@ -17,6 +17,27 @@ dependencies {
|
|||||||
compile 'com.android.support:support-v13:21.0.2'
|
compile 'com.android.support:support-v13:21.0.2'
|
||||||
compile 'net.sourceforge.htmlcleaner:htmlcleaner:2.10'
|
compile 'net.sourceforge.htmlcleaner:htmlcleaner:2.10'
|
||||||
compile 'de.cketti.library.changelog:ckchangelog:1.2.1'
|
compile 'de.cketti.library.changelog:ckchangelog:1.2.1'
|
||||||
|
|
||||||
|
androidTestCompile ('com.jakewharton.espresso:espresso:1.1-r3' ) {
|
||||||
|
// Note: some of these exclusions may become necessary. See the
|
||||||
|
// github site https://github.com/JakeWharton/double-espresso
|
||||||
|
//exclude group: 'com.squareup.dagger'
|
||||||
|
//exclude group: 'javax.inject'
|
||||||
|
//exclude group: 'javax.annotation'
|
||||||
|
//exclude group: 'com.google.guava'
|
||||||
|
//exclude group: 'org.hamcrest'
|
||||||
|
exclude group: 'com.google.code.findbugs'
|
||||||
|
}
|
||||||
|
|
||||||
|
androidTestCompile("com.icegreen:greenmail:1.3.1b") {
|
||||||
|
// Use a better, later version
|
||||||
|
exclude group: "javax.mail"
|
||||||
|
}
|
||||||
|
|
||||||
|
// this version avoids some "Ignoring InnerClasses attribute for an anonymous inner class" warnings
|
||||||
|
androidTestCompile "javax.mail:javax.mail-api:1.5.2"
|
||||||
|
|
||||||
|
androidTestCompile "com.madgag.spongycastle:pg:1.51.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -26,6 +47,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 15
|
minSdkVersion 15
|
||||||
targetSdkVersion 17
|
targetSdkVersion 17
|
||||||
|
|
||||||
|
testInstrumentationRunner "com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
@ -56,6 +79,7 @@ android {
|
|||||||
exclude 'META-INF/LICENSE.txt'
|
exclude 'META-INF/LICENSE.txt'
|
||||||
exclude 'META-INF/NOTICE'
|
exclude 'META-INF/NOTICE'
|
||||||
exclude 'META-INF/NOTICE.txt'
|
exclude 'META-INF/NOTICE.txt'
|
||||||
|
exclude 'LICENSE.txt'
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
android:versionCode="1"
|
android:versionCode="1"
|
||||||
android:versionName="1.0">
|
android:versionName="1.0">
|
||||||
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/>
|
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/>
|
||||||
|
|
||||||
|
<!-- Allow debug trace to be written -->
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<!-- We add an application tag here just so that we can indicate that
|
<!-- We add an application tag here just so that we can indicate that
|
||||||
this package needs to link against the android.test library,
|
this package needs to link against the android.test library,
|
||||||
which is needed when building test cases. -->
|
which is needed when building test cases. -->
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.fsck.k9.activity.setup;
|
||||||
|
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.ConnectionSecurity;
|
||||||
|
import com.google.android.apps.common.testing.ui.espresso.matcher.BoundedMatcher;
|
||||||
|
import org.hamcrest.Description;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
|
||||||
|
import static com.google.android.apps.common.testing.testrunner.util.Checks.checkNotNull;
|
||||||
|
|
||||||
|
|
||||||
|
public class ConnectionSecurityHolderMatcher {
|
||||||
|
public static Matcher<Object> is(final ConnectionSecurity connectionSecurity) {
|
||||||
|
checkNotNull(connectionSecurity);
|
||||||
|
return new BoundedMatcher<Object, ConnectionSecurityHolder>(ConnectionSecurityHolder.class) {
|
||||||
|
@Override
|
||||||
|
public boolean matchesSafely(ConnectionSecurityHolder connectionSecurityHolder) {
|
||||||
|
return connectionSecurityHolder.connectionSecurity == connectionSecurity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void describeTo(Description description) {
|
||||||
|
description.appendText("connection security is: ");
|
||||||
|
description.appendText(connectionSecurity.name());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.fsck.k9.endtoend;
|
||||||
|
|
||||||
|
import com.fsck.k9.activity.setup.WelcomeMessage;
|
||||||
|
import com.fsck.k9.endtoend.pages.WelcomeMessagePage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new IMAP account via the getting started flow.
|
||||||
|
*/
|
||||||
|
public class A000_WelcomeAndSetupAccountIntegrationTest extends AbstractEndToEndTest<WelcomeMessage> {
|
||||||
|
|
||||||
|
public A000_WelcomeAndSetupAccountIntegrationTest() {
|
||||||
|
super(WelcomeMessage.class, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreateAccount() throws Exception {
|
||||||
|
new AccountSetupFlow(this).setupAccountFromWelcomePage(new WelcomeMessagePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreateSecondAccount() throws Exception {
|
||||||
|
new AccountSetupFlow(this).setupAccountFromWelcomePage(new WelcomeMessagePage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.fsck.k9.endtoend;
|
||||||
|
|
||||||
|
import com.fsck.k9.activity.Accounts;
|
||||||
|
import com.fsck.k9.endtoend.framework.AccountForTest;
|
||||||
|
import com.fsck.k9.endtoend.framework.ApplicationState;
|
||||||
|
import com.fsck.k9.endtoend.pages.AccountsPage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and removes accounts.
|
||||||
|
*
|
||||||
|
* Because of the way K-9 shows the start page, there must already be two accounts
|
||||||
|
* in existence for this test to work.
|
||||||
|
*/
|
||||||
|
public class A010_AccountIntegrationTest extends AbstractEndToEndTest<Accounts>{
|
||||||
|
|
||||||
|
public A010_AccountIntegrationTest() {
|
||||||
|
super(Accounts.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCreateAccountDirectly() throws Exception {
|
||||||
|
new AccountSetupFlow(this).setupAccountFromAccountsPage(new AccountsPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDeleteAccount() {
|
||||||
|
|
||||||
|
AccountsPage accountsPage = new AccountsPage();
|
||||||
|
|
||||||
|
AccountForTest accountForTest = ApplicationState.getInstance().accounts.get(0);
|
||||||
|
accountsPage.assertAccountExists(accountForTest.description);
|
||||||
|
|
||||||
|
accountsPage.clickLongOnAccount(accountForTest);
|
||||||
|
|
||||||
|
accountsPage.clickRemoveInAccountMenu();
|
||||||
|
|
||||||
|
accountsPage.clickOK();
|
||||||
|
|
||||||
|
accountsPage.assertAccountDoesNotExist(accountForTest.description);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package com.fsck.k9.endtoend;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.test.ActivityInstrumentationTestCase2;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
import com.fsck.k9.endtoend.framework.ApplicationState;
|
||||||
|
import com.fsck.k9.endtoend.framework.StubMailServer;
|
||||||
|
import com.fsck.k9.endtoend.pages.WelcomeMessagePage;
|
||||||
|
import com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions;
|
||||||
|
|
||||||
|
import junit.framework.AssertionFailedError;
|
||||||
|
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
|
||||||
|
|
||||||
|
public abstract class AbstractEndToEndTest<T extends Activity> extends ActivityInstrumentationTestCase2<T> {
|
||||||
|
|
||||||
|
private ApplicationState state = ApplicationState.getInstance();
|
||||||
|
private final boolean bypassWelcome;
|
||||||
|
|
||||||
|
public AbstractEndToEndTest(Class<T> activityClass) {
|
||||||
|
this(activityClass, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractEndToEndTest(Class<T> activityClass, boolean bypassWelcome) {
|
||||||
|
super(activityClass);
|
||||||
|
this.bypassWelcome = bypassWelcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
getActivity();
|
||||||
|
|
||||||
|
if (bypassWelcome) {
|
||||||
|
bypassWelcomeScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bypassWelcomeScreen() {
|
||||||
|
try {
|
||||||
|
onView(withId(R.id.welcome_message)).check(ViewAssertions.doesNotExist());
|
||||||
|
} catch (AssertionFailedError ex) {
|
||||||
|
/*
|
||||||
|
* The view doesn't NOT exist == the view exists, and needs to be bypassed!
|
||||||
|
*/
|
||||||
|
Log.d(getClass().getName(), "Bypassing welcome");
|
||||||
|
new AccountSetupFlow(this).setupAccountFromWelcomePage(new WelcomeMessagePage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StubMailServer setupMailServer() {
|
||||||
|
if (null == state.stubMailServer) {
|
||||||
|
state.stubMailServer = new StubMailServer();
|
||||||
|
}
|
||||||
|
return state.stubMailServer;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package com.fsck.k9.endtoend;
|
||||||
|
|
||||||
|
import com.fsck.k9.endtoend.framework.AccountForTest;
|
||||||
|
import com.fsck.k9.endtoend.framework.ApplicationState;
|
||||||
|
import com.fsck.k9.endtoend.framework.StubMailServer;
|
||||||
|
import com.fsck.k9.endtoend.framework.UserForImap;
|
||||||
|
import com.fsck.k9.endtoend.pages.AccountOptionsPage;
|
||||||
|
import com.fsck.k9.endtoend.pages.AccountSetupNamesPage;
|
||||||
|
import com.fsck.k9.endtoend.pages.AccountSetupPage;
|
||||||
|
import com.fsck.k9.endtoend.pages.AccountTypePage;
|
||||||
|
import com.fsck.k9.endtoend.pages.AccountsPage;
|
||||||
|
import com.fsck.k9.endtoend.pages.IncomingServerSettingsPage;
|
||||||
|
import com.fsck.k9.endtoend.pages.OutgoingServerSettingsPage;
|
||||||
|
import com.fsck.k9.endtoend.pages.WelcomeMessagePage;
|
||||||
|
import com.fsck.k9.mail.ConnectionSecurity;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulated the steps required to set up a new mail account.
|
||||||
|
*/
|
||||||
|
public class AccountSetupFlow {
|
||||||
|
|
||||||
|
static final String ACCOUNT_NAME = "sendAndReceiveTestName";
|
||||||
|
|
||||||
|
private final AbstractEndToEndTest test;
|
||||||
|
|
||||||
|
public AccountSetupFlow(AbstractEndToEndTest test) {
|
||||||
|
this.test = test;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountsPage setupAccountFromWelcomePage(WelcomeMessagePage welcomeMessagePage) {
|
||||||
|
AccountSetupPage accountSetupPage = welcomeMessagePage.clickNext();
|
||||||
|
return setupAccountFromSetupNewAccountActivity(accountSetupPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountsPage setupAccountFromAccountsPage(AccountsPage accountPage) {
|
||||||
|
AccountSetupPage accountSetupPage = accountPage.clickAddNewAccount();
|
||||||
|
return setupAccountFromSetupNewAccountActivity(accountSetupPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountsPage setupAccountFromSetupNewAccountActivity(AccountSetupPage accountSetupPage) {
|
||||||
|
AccountTypePage accountTypePage = fillInCredentialsAndClickManualSetup(accountSetupPage);
|
||||||
|
|
||||||
|
IncomingServerSettingsPage incoming = accountTypePage.clickImap();
|
||||||
|
|
||||||
|
StubMailServer stubMailServer = test.setupMailServer();
|
||||||
|
|
||||||
|
OutgoingServerSettingsPage outgoing = setupIncomingServerAndClickNext(incoming, stubMailServer);
|
||||||
|
|
||||||
|
AccountOptionsPage accountOptionsPage = setupOutgoingServerAndClickNext(outgoing, stubMailServer);
|
||||||
|
|
||||||
|
AccountSetupNamesPage accountSetupNamesPage = accountOptionsPage.clickNext();
|
||||||
|
|
||||||
|
String accountDescription = tempAccountName();
|
||||||
|
accountSetupNamesPage.inputAccountDescription(accountDescription);
|
||||||
|
accountSetupNamesPage.inputAccountName(ACCOUNT_NAME);
|
||||||
|
|
||||||
|
AccountsPage accountsPage = accountSetupNamesPage.clickDone();
|
||||||
|
|
||||||
|
accountsPage.assertAccountExists(accountDescription);
|
||||||
|
|
||||||
|
ApplicationState.getInstance().accounts.add(new AccountForTest(ACCOUNT_NAME, accountDescription, stubMailServer));
|
||||||
|
|
||||||
|
return accountsPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String tempAccountName() {
|
||||||
|
return "sendAndReceiveTest-" + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").format(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccountTypePage fillInCredentialsAndClickManualSetup(AccountSetupPage page) {
|
||||||
|
return page
|
||||||
|
.inputEmailAddress(UserForImap.TEST_USER.emailAddress)
|
||||||
|
.inputPassword(UserForImap.TEST_USER.password)
|
||||||
|
.clickManualSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccountOptionsPage setupOutgoingServerAndClickNext(OutgoingServerSettingsPage page, StubMailServer stubMailServer) {
|
||||||
|
return page
|
||||||
|
.inputSmtpServer(stubMailServer.getSmtpBindAddress())
|
||||||
|
.inputSmtpSecurity(ConnectionSecurity.NONE)
|
||||||
|
.inputPort(stubMailServer.getSmtpPort())
|
||||||
|
.inputRequireSignIn(false)
|
||||||
|
.clickNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutgoingServerSettingsPage setupIncomingServerAndClickNext(IncomingServerSettingsPage page, StubMailServer stubMailServer) {
|
||||||
|
return page
|
||||||
|
.inputImapServer(stubMailServer.getImapBindAddress())
|
||||||
|
.inputImapSecurity(ConnectionSecurity.NONE)
|
||||||
|
.inputPort(stubMailServer.getImapPort())
|
||||||
|
.inputUsername(UserForImap.TEST_USER.loginUsername)
|
||||||
|
.clickNext();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.fsck.k9.endtoend.framework;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An account that was added by a test.
|
||||||
|
*/
|
||||||
|
public class AccountForTest {
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
public final String description;
|
||||||
|
public final StubMailServer stubMailServer;
|
||||||
|
|
||||||
|
public AccountForTest(String name, String description, StubMailServer stubMailServer) {
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
this.stubMailServer = stubMailServer;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.fsck.k9.endtoend.framework;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the state of the application from the point of view of end-to-end tests.
|
||||||
|
*/
|
||||||
|
public class ApplicationState {
|
||||||
|
|
||||||
|
private static final ApplicationState state = new ApplicationState();
|
||||||
|
|
||||||
|
public final List<AccountForTest> accounts = new ArrayList<AccountForTest>();
|
||||||
|
|
||||||
|
public StubMailServer stubMailServer;
|
||||||
|
|
||||||
|
public static ApplicationState getInstance() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.fsck.k9.endtoend.framework;
|
||||||
|
|
||||||
|
import com.icegreen.greenmail.util.GreenMail;
|
||||||
|
import com.icegreen.greenmail.util.ServerSetup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration and management of a pair of stub servers for use by an account.
|
||||||
|
*/
|
||||||
|
public class StubMailServer {
|
||||||
|
private static final ServerSetup IMAP_SERVER_SETUP = new ServerSetup(10143, "127.0.0.2", ServerSetup.PROTOCOL_IMAP);
|
||||||
|
private static final ServerSetup SMTP_SERVER_SETUP = new ServerSetup(10587, "127.0.0.2", ServerSetup.PROTOCOL_SMTP);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub server that speaks SMTP, IMAP etc., that K-9 can talk to.
|
||||||
|
*/
|
||||||
|
private GreenMail greenmail;
|
||||||
|
|
||||||
|
public StubMailServer() {
|
||||||
|
|
||||||
|
greenmail = new GreenMail(new ServerSetup[]{IMAP_SERVER_SETUP, SMTP_SERVER_SETUP});
|
||||||
|
greenmail.setUser(UserForImap.TEST_USER.emailAddress, UserForImap.TEST_USER.loginUsername, UserForImap.TEST_USER.password);
|
||||||
|
greenmail.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSmtpBindAddress() {
|
||||||
|
return SMTP_SERVER_SETUP.getBindAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSmtpPort() {
|
||||||
|
return SMTP_SERVER_SETUP.getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImapBindAddress() {
|
||||||
|
return IMAP_SERVER_SETUP.getBindAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getImapPort() {
|
||||||
|
return IMAP_SERVER_SETUP.getPort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.fsck.k9.endtoend.framework;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Credentials for the stub IMAP/SMTP server
|
||||||
|
*/
|
||||||
|
public class UserForImap {
|
||||||
|
|
||||||
|
public static final UserForImap TEST_USER = new UserForImap("test-username", "test-password", "test-email@example.com");
|
||||||
|
|
||||||
|
public final String loginUsername;
|
||||||
|
public final String password;
|
||||||
|
public final String emailAddress;
|
||||||
|
|
||||||
|
private UserForImap(String loginUsername, String password, String emailAddress) {
|
||||||
|
this.loginUsername = loginUsername;
|
||||||
|
this.password = password;
|
||||||
|
this.emailAddress = emailAddress;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.fsck.k9.endtoend.pages;
|
||||||
|
|
||||||
|
public class AbstractPage {
|
||||||
|
|
||||||
|
// used to have some content. Now a placeholder class
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.fsck.k9.endtoend.pages;
|
||||||
|
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
|
||||||
|
|
||||||
|
|
||||||
|
public class AccountOptionsPage extends AbstractPage {
|
||||||
|
|
||||||
|
public AccountSetupNamesPage clickNext() {
|
||||||
|
onView(withId(R.id.next)).perform(click());
|
||||||
|
return new AccountSetupNamesPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.fsck.k9.endtoend.pages;
|
||||||
|
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
import com.google.android.apps.common.testing.ui.espresso.NoMatchingViewException;
|
||||||
|
import com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers;
|
||||||
|
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.clearText;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.scrollTo;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.typeText;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
|
||||||
|
|
||||||
|
|
||||||
|
public class AccountSetupNamesPage extends AbstractPage {
|
||||||
|
|
||||||
|
public AccountSetupNamesPage inputAccountName(String name) {
|
||||||
|
onView(withId(R.id.account_name))
|
||||||
|
.perform(scrollTo())
|
||||||
|
.perform(clearText())
|
||||||
|
.perform(typeText(name));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountSetupNamesPage inputAccountDescription(String name) {
|
||||||
|
onView(withId(R.id.account_description))
|
||||||
|
.perform(scrollTo())
|
||||||
|
.perform(clearText())
|
||||||
|
.perform(typeText(name));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountsPage clickDone() {
|
||||||
|
onView(withId(R.id.done))
|
||||||
|
.perform(click());
|
||||||
|
dismissChangelog();
|
||||||
|
return new AccountsPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dismissChangelog() {
|
||||||
|
try {
|
||||||
|
onView(ViewMatchers.withText("OK")).perform(click());
|
||||||
|
} catch (NoMatchingViewException ex) {
|
||||||
|
// Ignored. Not the best way of doing this, but Espresso rightly makes
|
||||||
|
// conditional flow difficult.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.fsck.k9.endtoend.pages;
|
||||||
|
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.typeText;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
|
||||||
|
|
||||||
|
public class AccountSetupPage extends AbstractPage {
|
||||||
|
|
||||||
|
public AccountSetupPage inputEmailAddress(String emailAddress) {
|
||||||
|
onView(withId(R.id.account_email)).perform(typeText(emailAddress));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountSetupPage inputPassword(String password) {
|
||||||
|
onView(withId(R.id.account_password)).perform(typeText(password));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountTypePage clickManualSetup() {
|
||||||
|
onView(withId(R.id.manual_setup)).perform(click());
|
||||||
|
return new AccountTypePage();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.fsck.k9.endtoend.pages;
|
||||||
|
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
|
||||||
|
public class AccountTypePage extends AbstractPage {
|
||||||
|
|
||||||
|
public IncomingServerSettingsPage clickImap() {
|
||||||
|
onView(withId(R.id.imap)).perform(click());
|
||||||
|
return new IncomingServerSettingsPage();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package com.fsck.k9.endtoend.pages;
|
||||||
|
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
import com.fsck.k9.endtoend.framework.AccountForTest;
|
||||||
|
import com.google.android.apps.common.testing.ui.espresso.NoMatchingViewException;
|
||||||
|
import com.google.android.apps.common.testing.ui.espresso.ViewAssertion;
|
||||||
|
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.longClick;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.scrollTo;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions.doesNotExist;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withText;
|
||||||
|
|
||||||
|
public class AccountsPage extends AbstractPage {
|
||||||
|
|
||||||
|
private void assertAccount(String accountDisplayName, boolean exists) {
|
||||||
|
ViewAssertion assertion = exists ? matches(isDisplayed()) : doesNotExist();
|
||||||
|
onView(withText(accountDisplayName)).check(assertion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountSetupPage clickAddNewAccount() {
|
||||||
|
// need to click twice for some reason?
|
||||||
|
onView(withId(R.id.add_new_account)).perform(click());
|
||||||
|
try {
|
||||||
|
onView(withId(R.id.add_new_account)).perform(click());
|
||||||
|
} catch (NoMatchingViewException ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
onView(withId(R.id.account_email)).perform(scrollTo());
|
||||||
|
return new AccountSetupPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertAccountExists(String accountDisplayName) {
|
||||||
|
assertAccount(accountDisplayName, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertAccountDoesNotExist(String accountDisplayName) {
|
||||||
|
assertAccount(accountDisplayName, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clickLongOnAccount(AccountForTest accountForTest) {
|
||||||
|
onView(withText(accountForTest.description)).perform(longClick());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clickRemoveInAccountMenu() {
|
||||||
|
onView(withText("Remove account")).perform(click());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clickOK() {
|
||||||
|
onView(withText("OK")).perform(click());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package com.fsck.k9.endtoend.pages;
|
||||||
|
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
import com.fsck.k9.mail.ConnectionSecurity;
|
||||||
|
|
||||||
|
import static com.fsck.k9.activity.setup.ConnectionSecurityHolderMatcher.is;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onData;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.clearText;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.scrollTo;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.typeText;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions.matches;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isClickable;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withText;
|
||||||
|
|
||||||
|
public class IncomingServerSettingsPage extends AbstractPage {
|
||||||
|
|
||||||
|
public IncomingServerSettingsPage inputImapServer(String imapServer) {
|
||||||
|
onView(withId(R.id.account_server))
|
||||||
|
.perform(scrollTo())
|
||||||
|
.perform(clearText())
|
||||||
|
.perform(typeText(imapServer));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncomingServerSettingsPage inputImapSecurity(ConnectionSecurity security) {
|
||||||
|
onView(withId(R.id.account_security_type))
|
||||||
|
.perform(scrollTo())
|
||||||
|
.perform(click());
|
||||||
|
onData(is(security)).perform(click());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncomingServerSettingsPage inputPort(int port) {
|
||||||
|
onView(withId(R.id.account_port))
|
||||||
|
.perform(scrollTo())
|
||||||
|
.perform(clearText())
|
||||||
|
.perform(typeText(String.valueOf(port)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public OutgoingServerSettingsPage clickNext() {
|
||||||
|
onView(withId(R.id.next))
|
||||||
|
// .perform(scrollTo())
|
||||||
|
.check(matches(isClickable()))
|
||||||
|
.perform(click());
|
||||||
|
|
||||||
|
// We know this view is on the next page, this functions as a wait.
|
||||||
|
onView(withText("SMTP server")).perform(scrollTo());
|
||||||
|
return new OutgoingServerSettingsPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IncomingServerSettingsPage inputUsername(String loginUsername) {
|
||||||
|
onView(withId(R.id.account_username))
|
||||||
|
.perform(scrollTo())
|
||||||
|
.perform(clearText())
|
||||||
|
.perform(typeText(loginUsername));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
package com.fsck.k9.endtoend.pages;
|
||||||
|
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
import com.fsck.k9.mail.ConnectionSecurity;
|
||||||
|
|
||||||
|
import static com.fsck.k9.activity.setup.ConnectionSecurityHolderMatcher.is;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onData;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.clearText;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.scrollTo;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.typeText;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
|
||||||
|
|
||||||
|
|
||||||
|
public class OutgoingServerSettingsPage extends AbstractPage {
|
||||||
|
|
||||||
|
public OutgoingServerSettingsPage inputSmtpServer(String serverAddress) {
|
||||||
|
onView(withId(R.id.account_server))
|
||||||
|
.perform(scrollTo())
|
||||||
|
.perform(clearText())
|
||||||
|
.perform(typeText(serverAddress));
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutgoingServerSettingsPage inputSmtpSecurity(ConnectionSecurity security) {
|
||||||
|
onView(withId(R.id.account_security_type))
|
||||||
|
.perform(scrollTo())
|
||||||
|
.perform(click());
|
||||||
|
onData(is(security)).perform(click());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutgoingServerSettingsPage inputPort(int port) {
|
||||||
|
onView(withId(R.id.account_port))
|
||||||
|
.perform(scrollTo())
|
||||||
|
.perform(clearText())
|
||||||
|
.perform(typeText(String.valueOf(port)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutgoingServerSettingsPage inputRequireSignIn(boolean requireSignInInput) {
|
||||||
|
onView(withId(R.id.account_require_login))
|
||||||
|
.perform(scrollTo());
|
||||||
|
/*
|
||||||
|
* Make this smarter; click if necessary.
|
||||||
|
*/
|
||||||
|
if (!requireSignInInput) {
|
||||||
|
onView(withId(R.id.account_require_login))
|
||||||
|
.perform(click());
|
||||||
|
}
|
||||||
|
// Matcher<View> checkedOrNot = requireSignInInput ? isChecked(): isNotChecked();
|
||||||
|
// try {
|
||||||
|
// onView(withId(R.id.account_require_login)).check((matches(checkedOrNot)));
|
||||||
|
// } catch (AssertionFailedWithCauseError ex) {
|
||||||
|
// onView(withId(R.id.account_require_login)).perform(click());
|
||||||
|
// }
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountOptionsPage clickNext() {
|
||||||
|
onView(withId(R.id.next)).perform(click());
|
||||||
|
return new AccountOptionsPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.fsck.k9.endtoend.pages;
|
||||||
|
|
||||||
|
import com.fsck.k9.R;
|
||||||
|
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
|
||||||
|
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
|
||||||
|
|
||||||
|
public class WelcomeMessagePage extends AbstractPage {
|
||||||
|
|
||||||
|
public final AccountSetupPage clickNext() {
|
||||||
|
onView(withId(R.id.next)).perform(click());
|
||||||
|
return new AccountSetupPage();
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import java.io.OutputStream;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
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;
|
||||||
@ -199,7 +200,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"
|
||||||
@ -208,7 +210,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"
|
||||||
@ -236,7 +239,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"
|
||||||
@ -245,7 +249,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"
|
||||||
@ -315,8 +320,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 {
|
||||||
@ -348,7 +352,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;
|
||||||
}
|
}
|
||||||
@ -356,12 +360,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 {
|
||||||
|
@ -0,0 +1,269 @@
|
|||||||
|
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;
|
||||||
|
import org.spongycastle.openpgp.PGPCompressedData;
|
||||||
|
import org.spongycastle.openpgp.PGPException;
|
||||||
|
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||||
|
import org.spongycastle.openpgp.PGPPublicKey;
|
||||||
|
import org.spongycastle.openpgp.PGPPublicKeyRingCollection;
|
||||||
|
import org.spongycastle.openpgp.PGPSignature;
|
||||||
|
import org.spongycastle.openpgp.PGPSignatureList;
|
||||||
|
import org.spongycastle.openpgp.PGPUtil;
|
||||||
|
import org.spongycastle.openpgp.bc.BcPGPObjectFactory;
|
||||||
|
import org.spongycastle.openpgp.bc.BcPGPPublicKeyRingCollection;
|
||||||
|
import org.spongycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
|
||||||
|
|
||||||
|
|
||||||
|
public class PgpMimeMessageTest extends AndroidTestCase {
|
||||||
|
private static final String PUBLIC_KEY = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
||||||
|
"Version: GnuPG v1\n" +
|
||||||
|
"\n" +
|
||||||
|
"mQINBE49+OsBEADIu2zVIYllkqLYaCZq2d8r80titzegJiXTaW8fRS0FKGE7KmNt\n" +
|
||||||
|
"tWvWdiyLqvWlP4Py9OZPmEBdz8AaPxqCFmVZfJimf28CW0wz2sRCYmmbQqaHFfpD\n" +
|
||||||
|
"rK+EJofckOu2j81coaFVLbvkvUNhWU7/DKyv4+EBFt9fjxptbfpNKttwI0aeUVCa\n" +
|
||||||
|
"+Z/m18+OLpeE33BXd5POrBb4edAlMCwKk8m4nDXJ3B+KmR0qfCLB79gqEjsDLl+y\n" +
|
||||||
|
"65NcRk5uxIk53NRXHkmQujX1bsf5VFLha4KbUaB7BCtcSi1rY99WXfO/PWzTelOh\n" +
|
||||||
|
"pKDIRq+v3Kl21TipY0t4kco4AUlIx5b1F0EHPpmIDr0gEheZBali5c9wUR8czc/H\n" +
|
||||||
|
"aNkRP81hTPeBtUqp1S7GtJfcuWv6dyfBBVlnev98PCKOJo05meVwf3hkOLrciTfo\n" +
|
||||||
|
"1yuy/9hF18u3GhL8HLrxMQksLhD6sPzDto4jJQDxKAa7v9aLoR7oIdeWkn1TU61E\n" +
|
||||||
|
"ODR/254BRMoq619hqJwSNt6yOjGT2BBvlwbKdS8Xfw7SsBGGW8WnVJrqFCusfjSm\n" +
|
||||||
|
"DBdV/KWstRnOMqw4nhAwNFfXmAL2L8a+rLHxalFggfGcvVpzDhJyTg+/R1y3JMCo\n" +
|
||||||
|
"FfdFuhOTfkMqjGx8FgTmINOt54Wf9Xg6W0hQh3i98Wza3n8NuSPQJtAdqQARAQAB\n" +
|
||||||
|
"tBVja2V0dGkgPGNrQGNrZXR0aS5kZT6JAhwEEAECAAYFAk+6naAACgkQctTBoSHq\n" +
|
||||||
|
"3aHS+g/+MNxxfoEK+zopjWgvJmigOvejIpBWsLYJOJrpOgQuA61dQnQg0eLXPMDc\n" +
|
||||||
|
"xQTrPtIlkn7idtLbaG2FScheOS0RdApL8UJTiU18dzjHUWsLLhEFhOAgw/kqcdG0\n" +
|
||||||
|
"A95apNucybWU9jxynN9arxU6U+HZ67/JKxRjfdPxm+CmjiQwFPU9d6kGU/D08y58\n" +
|
||||||
|
"1VIn7IopHlbqOYRuQcX0p6Q642oRBp4b6+ggov21mgqscKe/eBQ8yUxf61eywLbb\n" +
|
||||||
|
"On63fkF1vl/RvsVcOnxcPLxUH4vmhuGPJ546RN7CCNjVF0QvuH9R8dnxS7/+rLe7\n" +
|
||||||
|
"BVtZ/8sAy9r8LvnehZWVww4Wo9haVQxB69+ns63lEb+dzbBmsKbGvQ98S/Hs62Wj\n" +
|
||||||
|
"nkMy7k+xzoRMa7tbKEtwwppxJVVSW//CVvEsS7DyaZna0udLh16MBCbMDzfAa3T4\n" +
|
||||||
|
"PmgQPmV1BeysHcFOn3p6p2ZRcQGEdvMBYUjqxxExstwZEY8nGagvG7j5YCJKzBNY\n" +
|
||||||
|
"xdBwkHXU3R3iM9o4aCKBsG2DMGHyhkHJXuGv9jFM32tAAf36qUJZ9eTKtoUt4xGt\n" +
|
||||||
|
"LuxgnkS830c7nZBfJARro75SDG9eew91u2aIDGO3aNXeOODrYl2KOWbpXg/NJDwS\n" +
|
||||||
|
"mlUZdwInb0PL6EDij1NtDiap2sIBKxtDjAeilS6vwS8s2P9HZdqJAkEEEwECACsC\n" +
|
||||||
|
"GyMFCRLMAwAGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheABQJOPftbAhkBAAoJEO4v\n" +
|
||||||
|
"7zp9qOKJG+oP/RBN5ahJCpwrk7U05J8x7UOPuP4UElMYoPYZCSp55mH6Xmr2C626\n" +
|
||||||
|
"DvTxhElz1WY7oIOJ7Mgp1RtGqZYV52d6fER10jowGbSkiFTvKb4PhQl4+AcGODMY\n" +
|
||||||
|
"LRVBw90rRhDSXzBQMeiyUf7Wse1jPsBfuOe6V1TsAtqjaAzrUDQOcsjQqW5ezvIj\n" +
|
||||||
|
"GNTFunX6wMUHzSBX6Lh0fLAp5ICp+l3agJ8S41Y4tSuFVil2IRX3o4vqxvU4f0C+\n" +
|
||||||
|
"KDIeJriLAMHajUp0V6VdisRHujjoTkZAGogJhNmNg0YH191a7AAKvVePgMQ/fsoW\n" +
|
||||||
|
"1hm9afwth/HOKvMx8fgKMwkn004V/to7qHByWDND33rgwlv1LYuvumEFd/paIABh\n" +
|
||||||
|
"dLhC6o6moVzwlOqhGfoD8DZAIzNCS4q2uCg8ik4temetPbCc5wMFtd+FO+FOb1tO\n" +
|
||||||
|
"/RahWeBfULreEijnv/zUZPetkJV9jTZXgXqCI9GCf6MTJrOLZ+G3hVxFyyHTKlWt\n" +
|
||||||
|
"iIzJHlX9rd3oQc7YJbdDFMZA+SdlGqiGdsjBmq0kcRqhhEa5QsnoNm9tuPuFnL5o\n" +
|
||||||
|
"GG7OFPztj9tr9ViRvsFBlx9jvmjRbRNF3287j1r+4lbGigsA1o8bRkLLXVSK1gCw\n" +
|
||||||
|
"bOLAPNJYH5bde6O+Qb8bepg9TByiohsFssxYXHwbgu/pcCMU1hCf15t4iQI+BBMB\n" +
|
||||||
|
"AgAoBQJOPfr+AhsjBQkSzAMABgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDu\n" +
|
||||||
|
"L+86fajiic/5EACHIaprlic0VKeV1H564KionZd7y3i3mX+e7Mdkd9QBFkb14UBw\n" +
|
||||||
|
"3RFnQhvq1MtaAC1lIYACYdIMF6/8LB1WQjB7kyt0IHbjEyodBVHq3U9n+mt+ZFy3\n" +
|
||||||
|
"6loA2r1odFJIaUWA2jBlBhtd3AQriANv0yciv4dPqPQfeAR5GxDiRbzGP1FZ47To\n" +
|
||||||
|
"PXZDHY9EKwaXo4q5D7XHzQy2aFe0IVUzXnofSE2KP9bu/wUU2DjZJ4cVXFdGFv5D\n" +
|
||||||
|
"xQ48UgXfhmPXSx1eeElDWdZHhH8BI7DOL66+FKm9PLiDYHUuVTvPxFSppu/+Gw5p\n" +
|
||||||
|
"gqDBwWEeKtJ1Yf3a5Vvbt+EK8BgC1/KaqY7A++dD2vM7w8PIKcf57WXF4O6KkIiW\n" +
|
||||||
|
"0M36eoAqAyuwqeTh3+mCWewegQBS2wORBYipbDf9OPTj/fsyCkaaXM2/wee79m+W\n" +
|
||||||
|
"+/67HVYlpIJPIKJIGs1N0PTl8WYZdaMLSL7nU/y3j51ytdidiKvRWl5X3MaCpp07\n" +
|
||||||
|
"T8MSogntMxXLU2zEnUqJjykXVpavFfXi1piw98qd+5wKMwiGLRq52z73N+q5nWk+\n" +
|
||||||
|
"5B2gqA3soXvloxXmoVuoTZDSnTjuQZk1kVl2XA+enE5rjVzpGte56QRYOGrjI9II\n" +
|
||||||
|
"SjH/PYLKSwjw8YzTeYFrv5UHegjU1G7auq5nJLsCupxADoRBw2y99Oiyg7QeY2tl\n" +
|
||||||
|
"dHRpIDxja2V0dGlAZ29vZ2xlbWFpbC5jb20+iQIcBBABAgAGBQJPup3IAAoJEHLU\n" +
|
||||||
|
"waEh6t2h1EoP/1Uw+cWK2lJU2BTwWuSTgL/SPoFoR+UKWQ7fES4eTZ330hHmWb4V\n" +
|
||||||
|
"Xpg+ZR6QYhXnJxMOMZ2tnya95GgdMJ+Hd4vlq6qb8746wmzIOt5XjhdMr3yiUsY9\n" +
|
||||||
|
"NC6P6ymuYEwuNMQBU/Z53rpuoFaF4Wc9nycK+3Gj6t3aPU0JX+qiFJl63+8GNw/Q\n" +
|
||||||
|
"CL+JQ4URQB3Vw/RADZfTBbT3VmrdSLGX2/I+nm64ysXvn6nt3q1JTHWXapPGrJXi\n" +
|
||||||
|
"HTlvjg+Niw38iBeHOkZ9Td5BIPBlj/8SXy9weG55ruTJFw0SXhV3VXIGbN0ZuJ3g\n" +
|
||||||
|
"nsusNCo4pJrFvJ0j3hzYrgOf/8jRUeesu7HlUPnYdBiJTNgKdCh5LlrKXlaisobl\n" +
|
||||||
|
"H33aufjO6i5HrX+/b1U9wE/G7MIzopcgiaeSYSJpO9huBJ0+Jri/4tdxvgT6aeNz\n" +
|
||||||
|
"9uL4rQKH2gUr9E89Np4aZ3zpp1QxfoJTVaR5AyJNaiiDOvZbvELYXK6QjAwgXIVr\n" +
|
||||||
|
"ScopPOXL1E+fdV9tsvYJfTbTJLZ9qeMRIOBPyhSbiDrB4r/i5zYyfydeEFVxackY\n" +
|
||||||
|
"vgSp++5HZt5lG0LFVjNnaPZETVCgVb5wmCxNsDqYV1fuxlAmPlTuXfMAvr+bxU/z\n" +
|
||||||
|
"3dmBDc7X1VfJVLzb0M5Z0KqvQlWTZkAkIPdQarJchvOBnFa7Rb6qFpcAiQI+BBMB\n" +
|
||||||
|
"AgAoAhsjBQkSzAMABgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAUCTj37WgAKCRDu\n" +
|
||||||
|
"L+86fajiiXgTD/9tA3FTGjiCE4Z0Nzi/Q+jmNJXGr/MQvgSlbKTGKJKkNk64kLTu\n" +
|
||||||
|
"HhYdbNhj/8419fINhxOzbetdWi+RUIRqk/FstBNGCbFYwNBbhp7jSToHLw1oESoN\n" +
|
||||||
|
"zPhxkuptvjyaEjrn50ydykVdTeMjytmZ3w7iu5eOt+tNS0x0thGfM3a4kdYoKW0v\n" +
|
||||||
|
"mp2BmrtUAXXsOJ475EK6IXeoGLMbgA+JtiDnWH12t/Dfl7L/6Nxjk1fGlihcJl6P\n" +
|
||||||
|
"Z1ZytDuRjnvlt77nqMaka7N+GadqmPUWonhKg/aGPMEgQUD4IWM/2Y2EpJIqVfB5\n" +
|
||||||
|
"Dv7llScCRB8mte/T8/dvpgr5B0KqGJDudb7Cgp+8zDGCU+M3uHU5ZQRlBO3bbCML\n" +
|
||||||
|
"nwT6BxmLT/6ufW7nT1eXscDi+DFKsLa6FQmDY38tzB6tyYlHxQU3RTkm4cLfDzI8\n" +
|
||||||
|
"/0JPRfx/RlKLW39QEmFJySMB3IVRtp5R0KNoKaAtYb5hRvD2JJJnx5q0u3h+me6j\n" +
|
||||||
|
"RzCMPJWxRKQjx0MdKEJedAH02XEqgeTunm7Kitb3aYuSykHUt2D/fgA4/CQoThF5\n" +
|
||||||
|
"SYUVbviYToEu/1hQAeHe9S1F92jCrjuTUmqejoVotk5O3uHBr7A3ASOoBrdaXxuS\n" +
|
||||||
|
"x9WpcRprfdtoD36TDWsSuarNxFVzcGFDaV2yN6mIf2LXTNgw2UAOHJzUqokCPgQT\n" +
|
||||||
|
"AQIAKAUCTj346wIbIwUJEswDAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ\n" +
|
||||||
|
"7i/vOn2o4onPNxAAq6jqkpbx0g/UIdNR2Q7mGQ0QJNbkt2P8Vq7jqwUu4td6GJdC\n" +
|
||||||
|
"vy4+RUWo5aRNQ3NgzRFkjLxIrTeSfK+yjruk01r3naGh6h0rk/EY1RCw1sA6GHVV\n" +
|
||||||
|
"gFcf83JtfgxH4NE8br+eiNnMODhOXG/UJsBMNo8bfyZu3FnJdUebCODMACJimKWb\n" +
|
||||||
|
"gBXa5EOnDZzXjYQrNRt95/yHse76V8JLdHqSnYPvVwcIT6MubF2NPspSFjfnFsj9\n" +
|
||||||
|
"J1Fb6aiI+3ob6HJNt2kyN0CdnnR/ZEZun8KQ37jJy7f5LXI6FDDT52oPBfddRRwy\n" +
|
||||||
|
"qZsmprbQjxUdIPKAYyjIELy+iAoFTrsJYvGNrgGMHI2ecyC2TE3uJ3qFALLhkFAS\n" +
|
||||||
|
"xYR+sSjAI3nJHPcfsfg10clrCfhh1KDWJjlVGgFjNd0MKIhLKA4kfwQvU4BSr5Al\n" +
|
||||||
|
"3fzflkRQuLDTNEeM9fwVW6ew+7IHpBNmYtnkSbmURcZoA4y8VuHH7qHID756kf4W\n" +
|
||||||
|
"u+wfNLf0SUZ1061y+PI77wUPUEVI2uJzo0xuHMG+L0TitRUv0zvaIGFt9ClX03FU\n" +
|
||||||
|
"6r1PPLGG1JNWuBORNgTJVIQzhLM3du7OnCdc4NhfOqZUfdWrIbgPEc870DnQSdmn\n" +
|
||||||
|
"J9OTF082SXEfEbjYzLuS5/aImXENypp6A7zeHBJ+TBJUNQj0c7S1qBeQGey0IUNo\n" +
|
||||||
|
"cmlzdGlhbiBLZXR0ZXJlciA8Y2tAY2tldHRpLmRlPokCPgQTAQIAKAUCU/eh2wIb\n" +
|
||||||
|
"IwUJEswDAAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ7i/vOn2o4omSGg/9\n" +
|
||||||
|
"EIj+Zz5rqC9BOC3sxbvyvZaPz0G6gT36i0ZW9Qe1drqxs7rcUelYPii8TPB/4v+H\n" +
|
||||||
|
"cx82qQpSnD6X7e8hNuRgsulkgZIhT/jnBFEJJoyMtt25UZIolj4JFpw1g+PRkufu\n" +
|
||||||
|
"KlVZCisJup+fFN2O6IcsfFqXxnaITWalFYMvOXwJ9rbT+2kczsH+MnxqeRvFQw7u\n" +
|
||||||
|
"Gy7Q3bu+A9+rntBhz/LPdzBOJBJh672Y9f9UVsmEFB66d84l7yUs1vlzi9DAqJK4\n" +
|
||||||
|
"y49hCe9QMv+NwL9rB9QxLdrbX724IRvGVwRzufd5jHOgAsYbizO+QltBJTsmRHvi\n" +
|
||||||
|
"yKClxuiUE4ygQyd5TT3ATC8wQKGfAGWRWaZoi3X6wWvKvW8cPg8ilMoTtPTlZuPL\n" +
|
||||||
|
"G32n0NaD7dacpmKfaLeopPAJgrnTl9LwPEDg4dwcSK+ETCY1BcoVGtOVxH1ghMd2\n" +
|
||||||
|
"IYOX+BSJiG39ApiHHBwPtc/PIqPjtR7MGB6dCldZZ46eHleCB8Re5HPrQAok+ijb\n" +
|
||||||
|
"XX0gx7ACYTniH+TsFszZyuLGstR8Cs8s7MwnbAX40506lDrj9c+0FE69/rJIMQsc\n" +
|
||||||
|
"wauGk1x1UaK2+gzBw28ymilhBbuOFabuStAHfGx/1niJMgBO4BiOPIBTjMOYtARR\n" +
|
||||||
|
"OSZ9dNGXkKYnxtN6T/kTO3F5N/fFJ42WjDWbrvfqDSy5Ag0ETj346wEQALEnw5y/\n" +
|
||||||
|
"zL3QAug9xuHktdVKCbxwAy8Q1ei5UA/GTGnTLdsHIN5e1B2bJyZaYcPTIT+xNgzP\n" +
|
||||||
|
"hwDQTosFFpg/JLP1xI28mShk8ai3ls73EhJLUGazOZ0ujxyMkWD0rIBMee6YkQMG\n" +
|
||||||
|
"zUkJKaEtqeVLci67Q8QLHLfE331JyTtd0gwlps6FAd7PuCl/50cayr0yXMx67iwK\n" +
|
||||||
|
"kyvXaLHYUjdK13MC2xoc4VrirzfNtX0JtCmAYoJ2i2Yq7vgLQasUjbzUsLUuwhol\n" +
|
||||||
|
"yoxwE6lB6paBdTh1dTa4mCN3Y8gM+CMveqQUcZuOyFZDWNtMPPCNeWWRkKgfc+fw\n" +
|
||||||
|
"HSiCHhDWu/7S6/xSqDb3qegXm6cAA2WFxJ+oEwTSRvK/89y6T3oiFbjmZs+sSRjr\n" +
|
||||||
|
"ZAsE3rDC2WFRUFBq6/V7+eO2F1fqNLPzXOaVQX9i3BHv4XjxC0PQoVFnvpSJlHSW\n" +
|
||||||
|
"Vuw5xA3Qqa8GuB80zWEqVBJ30gfqj1BAErpKwaVKJOuvRuQa2wkq7iXO/Io4S7UQ\n" +
|
||||||
|
"HFO+U9W87PaPNdfjxxEsVmexeXhF8l5zwHYyqKK0Pch/YDoUk/+w7Jn3cpmpceim\n" +
|
||||||
|
"YVEDr/YqrbvLpakHuEQiDgWZmcHHEVA7DbfsOULqq1vnpVq0TictdZ20Z8MJ2gAM\n" +
|
||||||
|
"P9HCZHPxLafI3YqQrXR3UIHb48Zwy9tdMv7NABEBAAGJAiUEGAECAA8FAk49+OsC\n" +
|
||||||
|
"GwwFCRLMAwAACgkQ7i/vOn2o4okF+BAAkN0Kd404HPy/35mCCdWm5DHpcxEURoY1\n" +
|
||||||
|
"X6mv6D+pvPQHUN9GKeYYT6wjcpsDsCn2UX9mp0e24SXOxZoVlJ7T6L/QN+MUwnt2\n" +
|
||||||
|
"LAO9XCZLMijhe7KX51FJjld1W9XfauqhPlR1Lzr9cJI3UdiYcsZH3X6SfW/hLLRE\n" +
|
||||||
|
"MWm/3YfACVVWNkG9PanhroNcVr925k/y58WRKdJOOgMGGBYyIAvtWb6m0Qn978AE\n" +
|
||||||
|
"53r7msHwZq06sPXIZJpCl6CTeyMrqU90G+JJY3BfP9rFsU9OLkDRrsAELleI9iXP\n" +
|
||||||
|
"QGw6Ixezdi93CqY+Y4weCjtYxm/5vKxwssg/ALVkM/VftWgWRSnZmnZwubgBzgwy\n" +
|
||||||
|
"wBwGHxPHz7CV3lBKZfw8U3L4Md3u1bMUu6Y+jW+322D+7+ZaLdJejmmJcEvLaItd\n" +
|
||||||
|
"c60IHTM/GbtV7TDiqQaRmyLY5KxnwGLthcYUsGI7HYDNqEa1+cRctB8lEWpgTjHK\n" +
|
||||||
|
"nwemvB5c1fPxao7w15O0tvSCX2kD5UMoAbvWJJvxcUTPTPBEHTYWrAk+Ny7CbdMA\n" +
|
||||||
|
"+71r942RXo9Xdm4hqjfMcDXdQmfjftfFB1rsBd5Qui8ideQP7ypllsWC8fJUkWN6\n" +
|
||||||
|
"3leW5gysLx9Mj6bu6XB4rYS1zH2keGtZe4Qqlxss7JPVsJzD9xSotg+G/Wb7F3HL\n" +
|
||||||
|
"HzpeeqkwzVU=\n" +
|
||||||
|
"=3yEX\n" +
|
||||||
|
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
||||||
|
|
||||||
|
|
||||||
|
public void testSignedMessage() throws IOException, MessagingException, PGPException {
|
||||||
|
String messageSource = "Date: Mon, 08 Dec 2014 17:44:18 +0100\r\n" +
|
||||||
|
"From: cketti <cketti@googlemail.com>\r\n" +
|
||||||
|
"MIME-Version: 1.0\r\n" +
|
||||||
|
"To: test@example.com\r\n" +
|
||||||
|
"Subject: OpenPGP signature test\r\n" +
|
||||||
|
"Content-Type: multipart/signed; micalg=pgp-sha1;\r\n" +
|
||||||
|
" protocol=\"application/pgp-signature\";\r\n" +
|
||||||
|
" boundary=\"24Bem7EnUI1Ipn9jNXuLgsetqa6wOkIxM\"\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"This is an OpenPGP/MIME signed message (RFC 4880 and 3156)\r\n" +
|
||||||
|
"--24Bem7EnUI1Ipn9jNXuLgsetqa6wOkIxM\r\n" +
|
||||||
|
"Content-Type: multipart/mixed;\r\n" +
|
||||||
|
" boundary=\"------------030308060900040601010501\"\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"This is a multi-part message in MIME format.\r\n" +
|
||||||
|
"--------------030308060900040601010501\r\n" +
|
||||||
|
"Content-Type: text/plain; charset=utf-8\r\n" +
|
||||||
|
"Content-Transfer-Encoding: quoted-printable\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"Message body\r\n" +
|
||||||
|
"goes here\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"--------------030308060900040601010501\r\n" +
|
||||||
|
"Content-Type: text/plain; charset=UTF-8;\r\n" +
|
||||||
|
" name=\"attachment.txt\"\r\n" +
|
||||||
|
"Content-Transfer-Encoding: base64\r\n" +
|
||||||
|
"Content-Disposition: attachment;\r\n" +
|
||||||
|
" filename=\"attachment.txt\"\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"VGV4dCBhdHRhY2htZW50Cg==\r\n" +
|
||||||
|
"--------------030308060900040601010501--\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"--24Bem7EnUI1Ipn9jNXuLgsetqa6wOkIxM\r\n" +
|
||||||
|
"Content-Type: application/pgp-signature; name=\"signature.asc\"\r\n" +
|
||||||
|
"Content-Description: OpenPGP digital signature\r\n" +
|
||||||
|
"Content-Disposition: attachment; filename=\"signature.asc\"\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"-----BEGIN PGP SIGNATURE-----\r\n" +
|
||||||
|
"Version: GnuPG v1\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"iQIcBAEBAgAGBQJUhdVqAAoJEO4v7zp9qOKJ8DQP/1+JE8UF7UmirnN1ZO+25hFC\r\n" +
|
||||||
|
"jAfFMxRWMWXN0gGB+6ySy6ah0bCwmRwHpRBsW/tNcsmOPKb2XBf9zwF06uk/lLp4\r\n" +
|
||||||
|
"ZmGXxSdQ9XJrlaHk8Sitn9Gi/1L+MNWgrsrLROAZv2jfc9wqN3FOrhN9NC1QXQvO\r\n" +
|
||||||
|
"+D7sMorSr3l94majoIDrzvxEnfJVfrZWNTUaulJofOJ55GBZ3UJNob1WKjrnculL\r\n" +
|
||||||
|
"IwmSERmVUoFBUfe/MBqqZH0WDJq9nt//NZFHLunj6nGsrpush1dQRcbR3zzQfXkk\r\n" +
|
||||||
|
"s7zDLDa8VUv6OxcefjsVN/O7EenoWWgNg6GfW6tY2+oUsLSP2OS3JXvYsylQP4hR\r\n" +
|
||||||
|
"iU1V9vvsu2Ax6bVb0+uTqw3jNiqVFy3o4mBigVUqp1EFIwBYmyNbe5wj4ACs9Avj\r\n" +
|
||||||
|
"9t2reFSfXobWQFUS4s71JeMefNAHHJWZI63wNTxE6LOw01YxdJiDaPWGTOyM75MK\r\n" +
|
||||||
|
"yqn7r5uIfeSv8NypGJaUv4firxKbrcZKk7Wpeh/rZuUSgoPcf3I1IzXfGKKIBHjU\r\n" +
|
||||||
|
"WUMhTF5SoC5kIZyeXvHrhTM8HszcS8EoG2XcmcYArwgCUlOunFwZNqLPsfdMTRL6\r\n" +
|
||||||
|
"9rcioaohEtroqoJiGAToJtIz8kqCaamnP/ASBkp9qqJizRd6fqt+tE8BsmJbuPLS\r\n" +
|
||||||
|
"6lBpS8j0TqmaZMYfB9u4\r\n" +
|
||||||
|
"=QvET\r\n" +
|
||||||
|
"-----END PGP SIGNATURE-----\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"--24Bem7EnUI1Ipn9jNXuLgsetqa6wOkIxM--\r\n";
|
||||||
|
|
||||||
|
BinaryTempFileBody.setTempDirectory(getContext().getCacheDir());
|
||||||
|
|
||||||
|
InputStream messageInputStream = new ByteArrayInputStream(messageSource.getBytes());
|
||||||
|
MimeMessage message;
|
||||||
|
try {
|
||||||
|
message = new MimeMessage(messageInputStream, true);
|
||||||
|
} finally {
|
||||||
|
messageInputStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Multipart multipartSigned = (Multipart) message.getBody();
|
||||||
|
|
||||||
|
BodyPart signedPart = multipartSigned.getBodyPart(0);
|
||||||
|
ByteArrayOutputStream signedPartOutputStream = new ByteArrayOutputStream();
|
||||||
|
signedPart.writeTo(signedPartOutputStream);
|
||||||
|
byte[] signedData = signedPartOutputStream.toByteArray();
|
||||||
|
|
||||||
|
Body signatureBody = multipartSigned.getBodyPart(1).getBody();
|
||||||
|
ByteArrayOutputStream signatureBodyOutputStream = new ByteArrayOutputStream();
|
||||||
|
signatureBody.writeTo(signatureBodyOutputStream);
|
||||||
|
byte[] signatureData = signatureBodyOutputStream.toByteArray();
|
||||||
|
|
||||||
|
assertTrue(verifySignature(signedData, signatureData));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean verifySignature(byte[] signedData, byte[] signatureData) throws IOException, PGPException {
|
||||||
|
InputStream signatureInputStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(signatureData));
|
||||||
|
PGPObjectFactory pgpObjectFactory = new BcPGPObjectFactory(signatureInputStream);
|
||||||
|
Object pgpObject = pgpObjectFactory.nextObject();
|
||||||
|
|
||||||
|
PGPSignatureList pgpSignatureList;
|
||||||
|
if (pgpObject instanceof PGPCompressedData) {
|
||||||
|
PGPCompressedData compressedData = (PGPCompressedData) pgpObject;
|
||||||
|
pgpObjectFactory = new BcPGPObjectFactory(compressedData.getDataStream());
|
||||||
|
pgpSignatureList = (PGPSignatureList) pgpObjectFactory.nextObject();
|
||||||
|
} else {
|
||||||
|
pgpSignatureList = (PGPSignatureList) pgpObject;
|
||||||
|
}
|
||||||
|
PGPSignature signature = pgpSignatureList.get(0);
|
||||||
|
|
||||||
|
InputStream keyInputStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(PUBLIC_KEY.getBytes()));
|
||||||
|
PGPPublicKeyRingCollection pgpPublicKeyRingCollection = new BcPGPPublicKeyRingCollection(keyInputStream);
|
||||||
|
PGPPublicKey publicKey = pgpPublicKeyRingCollection.getPublicKey(signature.getKeyID());
|
||||||
|
|
||||||
|
signature.init(new BcPGPContentVerifierBuilderProvider(), publicKey);
|
||||||
|
InputStream signedDataInputStream = new ByteArrayInputStream(signedData);
|
||||||
|
int ch;
|
||||||
|
while ((ch = signedDataInputStream.read()) >= 0) {
|
||||||
|
signature.update((byte) ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
signedDataInputStream.close();
|
||||||
|
keyInputStream.close();
|
||||||
|
signatureInputStream.close();
|
||||||
|
|
||||||
|
return signature.verify();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import com.fsck.k9.mail.MessagingException;
|
|||||||
import com.fsck.k9.mail.Message.RecipientType;
|
import com.fsck.k9.mail.Message.RecipientType;
|
||||||
import com.fsck.k9.mail.internet.MimeBodyPart;
|
import com.fsck.k9.mail.internet.MimeBodyPart;
|
||||||
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.TextBody;
|
import com.fsck.k9.mail.internet.TextBody;
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ public class LocalMessageExtractorTest 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 = extractTextAndAttachments(getContext(), message);
|
ViewableContainer container = extractTextAndAttachments(getContext(), message);
|
||||||
@ -50,7 +51,7 @@ public class LocalMessageExtractorTest 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 = extractTextAndAttachments(getContext(), message);
|
ViewableContainer container = extractTextAndAttachments(getContext(), message);
|
||||||
@ -80,7 +81,7 @@ public class LocalMessageExtractorTest 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 = extractTextAndAttachments(getContext(), message);
|
ViewableContainer container = extractTextAndAttachments(getContext(), message);
|
||||||
@ -124,7 +125,7 @@ public class LocalMessageExtractorTest 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();
|
||||||
@ -136,7 +137,7 @@ public class LocalMessageExtractorTest 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 = extractTextAndAttachments(getContext(), message);
|
ViewableContainer container = extractTextAndAttachments(getContext(), message);
|
||||||
|
@ -105,6 +105,7 @@ import com.fsck.k9.mail.internet.MessageExtractor;
|
|||||||
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;
|
||||||
@ -1400,10 +1401,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.
|
||||||
@ -1411,10 +1412,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +80,7 @@ import com.fsck.k9.mail.Store;
|
|||||||
import com.fsck.k9.mail.Transport;
|
import com.fsck.k9.mail.Transport;
|
||||||
import com.fsck.k9.mail.internet.MessageExtractor;
|
import com.fsck.k9.mail.internet.MessageExtractor;
|
||||||
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.mailstore.MessageRemovalListener;
|
import com.fsck.k9.mailstore.MessageRemovalListener;
|
||||||
@ -2697,7 +2698,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);
|
||||||
|
|
||||||
@ -3190,7 +3191,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);
|
||||||
|
@ -17,6 +17,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;
|
||||||
|
|
||||||
@ -784,17 +785,16 @@ public class LocalFolder extends Folder<LocalMessage> 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 &&
|
} else if (mp.getCount() == 1 &&
|
||||||
!(mp.getBodyPart(0) instanceof LocalAttachmentBodyPart))
|
!(mp.getBodyPart(0) instanceof LocalAttachmentBodyPart)) {
|
||||||
{
|
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1566,7 +1566,7 @@ public class LocalFolder extends Folder<LocalMessage> 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);
|
||||||
@ -1648,11 +1648,13 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
|
|||||||
getAccount(),
|
getAccount(),
|
||||||
attachmentId);
|
attachmentId);
|
||||||
if (MimeUtil.isMessage(attachment.getMimeType())) {
|
if (MimeUtil.isMessage(attachment.getMimeType())) {
|
||||||
attachment.setBody(new LocalAttachmentMessageBody(
|
LocalAttachmentMessageBody body = new LocalAttachmentMessageBody(
|
||||||
contentUri, LocalFolder.this.localStore.context));
|
contentUri, LocalFolder.this.localStore.context);
|
||||||
|
MimeMessageHelper.setBody(attachment, body);
|
||||||
} else {
|
} else {
|
||||||
attachment.setBody(new LocalAttachmentBody(
|
LocalAttachmentBody body = new LocalAttachmentBody(
|
||||||
contentUri, LocalFolder.this.localStore.context));
|
contentUri, LocalFolder.this.localStore.context);
|
||||||
|
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);
|
||||||
|
@ -514,6 +514,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 MessagingException {
|
public void setHeader(String name, String value) throws MessagingException {
|
||||||
if (!mHeadersLoaded)
|
if (!mHeadersLoaded)
|
||||||
|
@ -44,7 +44,7 @@ public class MimeMessageParseTest {
|
|||||||
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())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -63,7 +63,7 @@ public class MimeMessageParseTest {
|
|||||||
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())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -82,7 +82,7 @@ public class MimeMessageParseTest {
|
|||||||
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())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -238,7 +238,7 @@ public class MimeMessageParseTest {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user