move crypto data into an annotation structure, and fix pgp/inline

note that we currently lack proper confirmation about whether data was
actually decrypted or not, so for now we always assume it wasn't
This commit is contained in:
Vincent Breitmoser 2015-01-30 16:10:47 +01:00
parent 4bec165fdc
commit dc8fd39c7e
7 changed files with 147 additions and 94 deletions

View File

@ -83,7 +83,7 @@ public class MessageDecryptVerifier {
String mimeType = part.getMimeType();
Body body = part.getBody();
if (TEXT_PLAIN.equals(mimeType)) {
if (TEXT_PLAIN.equalsIgnoreCase(mimeType)) {
String text = MessageExtractor.getTextFromPart(part);
switch (OpenPgpUtils.parseMessage(text)) {
case OpenPgpUtils.PARSE_RESULT_MESSAGE:
@ -120,6 +120,12 @@ public class MessageDecryptVerifier {
return null;
}
// Note: This method should ONLY be used to differentiate parts
// already filtered with the methods above!
public static boolean isPgpInlinePart(Part part) {
return TEXT_PLAIN.equals(part.getMimeType());
}
public static boolean isPgpMimePart(Part part) {
return isPgpMimeSignedPart(part) || isPgpMimeEncryptedPart(part);
}

View File

@ -35,15 +35,15 @@ import org.apache.james.mime4j.stream.Field;
import org.apache.james.mime4j.stream.MimeConfig;
import org.apache.james.mime4j.util.MimeUtil;
// TODO rename this class? this class doesn't really bear any 'decrypted' semantics anymore...
public class DecryptStreamParser {
private static final String DECRYPTED_CACHE_DIRECTORY = "decrypted";
public static OpenPgpResultBodyPart parse(Context context, InputStream inputStream) throws MessagingException, IOException {
public static MimeBodyPart parse(Context context, InputStream inputStream) throws MessagingException, IOException {
File decryptedTempDirectory = getDecryptedTempDirectory(context);
OpenPgpResultBodyPart decryptedRootPart = new OpenPgpResultBodyPart(true);
MimeBodyPart decryptedRootPart = new MimeBodyPart();
MimeConfig parserConfig = new MimeConfig();
parserConfig.setMaxHeaderLen(-1);
@ -111,10 +111,10 @@ public class DecryptStreamParser {
private static class PartBuilder implements ContentHandler {
private final File decryptedTempDirectory;
private final OpenPgpResultBodyPart decryptedRootPart;
private final MimeBodyPart decryptedRootPart;
private final Stack<Object> stack = new Stack<Object>();
public PartBuilder(File decryptedTempDirectory, OpenPgpResultBodyPart decryptedRootPart)
public PartBuilder(File decryptedTempDirectory, MimeBodyPart decryptedRootPart)
throws MessagingException {
this.decryptedTempDirectory = decryptedTempDirectory;
this.decryptedRootPart = decryptedRootPart;

View File

@ -6,7 +6,6 @@ import android.net.Uri;
import com.fsck.k9.R;
import com.fsck.k9.crypto.DecryptedTempFileBody;
import com.fsck.k9.crypto.MessageDecryptVerifier;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
@ -22,6 +21,7 @@ import com.fsck.k9.mail.internet.Viewable;
import com.fsck.k9.mailstore.MessageViewInfo.MessageViewContainer;
import com.fsck.k9.provider.AttachmentProvider;
import com.fsck.k9.provider.K9FileProvider;
import com.fsck.k9.ui.messageview.MessageCryptoHelper.MessageCryptoAnnotations;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
@ -45,7 +45,7 @@ public class LocalMessageExtractor {
private static final int FILENAME_PREFIX_LENGTH = FILENAME_PREFIX.length();
private static final String FILENAME_SUFFIX = " ";
private static final int FILENAME_SUFFIX_LENGTH = FILENAME_SUFFIX.length();
private static final OpenPgpResultBodyPart NO_SIGNATURE_RESULT = null;
private static final OpenPgpResultAnnotation NO_ANNOTATIONS = null;
private LocalMessageExtractor() {}
/**
@ -424,14 +424,22 @@ public class LocalMessageExtractor {
html.append("</td></tr>");
}
public static MessageViewInfo decodeMessageForView(Context context, Message message) throws MessagingException {
public static MessageViewInfo decodeMessageForView(Context context,
Message message, MessageCryptoAnnotations annotations) throws MessagingException {
// 1. break mime structure on encryption/signature boundaries
List<Part> parts = getCryptPieces(message);
List<Part> parts = getCryptPieces(message, annotations);
// 2. extract viewables/attachments of parts
ArrayList<MessageViewContainer> containers = new ArrayList<MessageViewContainer>();
for (Part part : parts) {
OpenPgpResultAnnotation pgpAnnotation = annotations.get(part);
// TODO properly handle decrypted data part - this just replaces the part
if (pgpAnnotation != NO_ANNOTATIONS && pgpAnnotation.hasOutputData()) {
part = pgpAnnotation.getOutputData();
}
ArrayList<Part> attachments = new ArrayList<Part>();
List<Viewable> viewables = MessageExtractor.getViewables(part, attachments);
@ -440,12 +448,11 @@ public class LocalMessageExtractor {
attachments);
List<AttachmentViewInfo> attachmentInfos = extractAttachmentInfos(context, attachments);
OpenPgpResultBodyPart resultBodyPart = getSignatureResultForPart(part);
if (resultBodyPart != NO_SIGNATURE_RESULT) {
OpenPgpSignatureResult pgpResult = resultBodyPart.getSignatureResult();
OpenPgpError pgpError = resultBodyPart.getError();
boolean wasEncrypted = resultBodyPart.wasEncrypted();
PendingIntent pendingIntent = resultBodyPart.getPendingIntent();
if (pgpAnnotation != NO_ANNOTATIONS) {
OpenPgpSignatureResult pgpResult = pgpAnnotation.getSignatureResult();
OpenPgpError pgpError = pgpAnnotation.getError();
boolean wasEncrypted = pgpAnnotation.wasEncrypted();
PendingIntent pendingIntent = pgpAnnotation.getPendingIntent();
containers.add(new MessageViewContainer(
viewable.html, attachmentInfos, pgpResult, pgpError, wasEncrypted, pendingIntent));
@ -458,7 +465,7 @@ public class LocalMessageExtractor {
return new MessageViewInfo(containers, message);
}
public static List<Part> getCryptPieces(Part part) throws MessagingException {
public static List<Part> getCryptPieces(Message message, MessageCryptoAnnotations annotations) throws MessagingException {
// TODO make sure this method does what it is supposed to
/* This method returns a list of mime parts which are to be parsed into
@ -470,14 +477,15 @@ public class LocalMessageExtractor {
ArrayList<Part> parts = new ArrayList<Part>();
if (!getCryptSubPieces(part, parts)) {
parts.add(part);
if (!getCryptSubPieces(message, parts, annotations)) {
parts.add(message);
}
return parts;
}
public static boolean getCryptSubPieces(Part part, ArrayList<Part> parts) throws MessagingException {
public static boolean getCryptSubPieces(Part part, ArrayList<Part> parts,
MessageCryptoAnnotations annotations) throws MessagingException {
Body body = part.getBody();
if (body instanceof Multipart) {
@ -485,46 +493,20 @@ public class LocalMessageExtractor {
if ("multipart/mixed".equals(part.getMimeType())) {
boolean foundSome = false;
for (BodyPart sub : multi.getBodyParts()) {
foundSome |= getCryptSubPieces(sub, parts);
foundSome |= getCryptSubPieces(sub, parts, annotations);
}
if (!foundSome) {
parts.add(part);
return true;
}
} else if (MessageDecryptVerifier.isPgpMimeSignedPart(part)) {
} else if (annotations.has(part)) {
parts.add(part);
return true;
} else if (isPgpMimeDecryptedPart(part)) {
parts.add(multi.getBodyPart(2));
return true;
}
}
return false;
}
public static boolean isPgpMimeDecryptedPart (Part part) {
Body body = part.getBody();
return (body instanceof Multipart)
&& MessageDecryptVerifier.isPgpMimeEncryptedPart(part)
&& ((Multipart) part.getBody()).getCount() == 3;
}
private static OpenPgpResultBodyPart getSignatureResultForPart(Part part) {
if (part instanceof OpenPgpResultBodyPart) {
OpenPgpResultBodyPart openPgpResultBodyPart = (OpenPgpResultBodyPart) part;
return openPgpResultBodyPart;
}
if (MessageDecryptVerifier.isPgpMimeSignedPart(part)) {
Multipart multi = (Multipart) part.getBody();
if (multi.getCount() == 3 && multi.getBodyPart(2) instanceof OpenPgpResultBodyPart) {
OpenPgpResultBodyPart openPgpResultBodyPart = (OpenPgpResultBodyPart) multi.getBodyPart(2);
return openPgpResultBodyPart;
}
}
return NO_SIGNATURE_RESULT;
}
private static List<AttachmentViewInfo> extractAttachmentInfos(Context context, List<Part> attachmentParts)
throws MessagingException {

View File

@ -3,21 +3,17 @@ package com.fsck.k9.mailstore;
import android.app.PendingIntent;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeBodyPart;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
public class OpenPgpResultAnnotation extends MimeBodyPart {
public class OpenPgpResultAnnotation {
private boolean wasEncrypted;
private OpenPgpSignatureResult signatureResult;
private OpenPgpError error;
private PendingIntent pendingIntent;
public OpenPgpResultAnnotation(boolean wasEncrypted) throws MessagingException {
this.wasEncrypted = wasEncrypted;
}
private MimeBodyPart outputData;
public OpenPgpSignatureResult getSignatureResult() {
return signatureResult;
@ -43,7 +39,24 @@ public class OpenPgpResultAnnotation extends MimeBodyPart {
this.error = error;
}
public boolean hasOutputData() {
return outputData != null;
}
public void setOutputData(MimeBodyPart outputData) {
this.outputData = outputData;
}
public MimeBodyPart getOutputData() {
return outputData;
}
public boolean wasEncrypted() {
return wasEncrypted;
}
public void setWasEncrypted(boolean wasEncrypted) {
this.wasEncrypted = wasEncrypted;
}
}

View File

@ -9,15 +9,18 @@ import com.fsck.k9.K9;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mailstore.LocalMessageExtractor;
import com.fsck.k9.mailstore.MessageViewInfo;
import com.fsck.k9.ui.messageview.MessageCryptoHelper.MessageCryptoAnnotations;
public class DecodeMessageLoader extends AsyncTaskLoader<MessageViewInfo> {
private final Message message;
private MessageViewInfo messageViewInfo;
private MessageCryptoAnnotations annotations;
public DecodeMessageLoader(Context context, Message message) {
public DecodeMessageLoader(Context context, Message message, MessageCryptoAnnotations annotations) {
super(context);
this.message = message;
this.annotations = annotations;
}
@Override
@ -40,7 +43,7 @@ public class DecodeMessageLoader extends AsyncTaskLoader<MessageViewInfo> {
@Override
public MessageViewInfo loadInBackground() {
try {
return LocalMessageExtractor.decodeMessageForView(getContext(), message);
return LocalMessageExtractor.decodeMessageForView(getContext(), message, annotations);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Error while decoding message", e);
return null;

View File

@ -1,11 +1,13 @@
package com.fsck.k9.ui.messageview;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@ -30,9 +32,11 @@ import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mailstore.DecryptStreamParser;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.OpenPgpResultBodyPart;
import com.fsck.k9.mailstore.OpenPgpResultAnnotation;
import org.openintents.openpgp.IOpenPgpService;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
@ -42,7 +46,7 @@ import org.openintents.openpgp.util.OpenPgpServiceConnection;
import org.openintents.openpgp.util.OpenPgpServiceConnection.OnBound;
class MessageCryptoHelper {
public class MessageCryptoHelper {
private final Context context;
private final MessageViewFragment fragment;
@ -54,12 +58,16 @@ class MessageCryptoHelper {
private Part currentlyDecrypringOrVerifyingPart;
private Intent currentCryptoResult;
private MessageCryptoAnnotations messageAnnotations;
private static final int INVALID_OPENPGP_RESULT_CODE = -1;
public MessageCryptoHelper(Context context, MessageViewFragment fragment, Account account) {
this.context = context;
this.fragment = fragment;
this.account = account;
this.messageAnnotations = new MessageCryptoAnnotations();
}
public void decryptOrVerifyMessagePartsIfNecessary(LocalMessage message) {
@ -149,6 +157,8 @@ class MessageCryptoHelper {
try {
if (MessageDecryptVerifier.isPgpMimeSignedPart(currentlyDecrypringOrVerifyingPart)) {
callAsyncDetachedVerify(intent);
} else if (MessageDecryptVerifier.isPgpInlinePart(currentlyDecrypringOrVerifyingPart)) {
callAsyncInlineOperation(intent);
} else {
callAsyncDecrypt(intent);
}
@ -159,6 +169,29 @@ class MessageCryptoHelper {
}
}
private void callAsyncInlineOperation(Intent intent) throws IOException {
final CountDownLatch latch = new CountDownLatch(1);
PipedInputStream pipedInputStream = getPipedInputStreamForEncryptedOrInlineData();
final ByteArrayOutputStream decryptedOutputStream = new ByteArrayOutputStream();
openPgpApi.executeApiAsync(intent, pipedInputStream, decryptedOutputStream, new IOpenPgpCallback() {
@Override
public void onReturn(Intent result) {
currentCryptoResult = result;
MimeBodyPart decryptedPart = null;
try {
TextBody body = new TextBody(new String(decryptedOutputStream.toByteArray()));
decryptedPart = new MimeBodyPart(body, "text/plain");
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "MessagingException", e);
}
onCryptoConverge(decryptedPart);
}
});
}
private void callAsyncDecrypt(Intent intent) throws IOException {
final CountDownLatch latch = new CountDownLatch(1);
PipedInputStream pipedInputStream = getPipedInputStreamForEncryptedOrInlineData();
@ -223,15 +256,17 @@ class MessageCryptoHelper {
@Override
public void run() {
try {
if (currentlyDecrypringOrVerifyingPart instanceof Multipart) {
if (MessageDecryptVerifier.isPgpMimePart(currentlyDecrypringOrVerifyingPart)) {
Multipart multipartEncryptedMultipart =
(Multipart) currentlyDecrypringOrVerifyingPart.getBody();
BodyPart encryptionPayloadPart = multipartEncryptedMultipart.getBodyPart(1);
Body encryptionPayloadBody = encryptionPayloadPart.getBody();
encryptionPayloadBody.writeTo(out);
} else {
} else if (MessageDecryptVerifier.isPgpInlinePart(currentlyDecrypringOrVerifyingPart)) {
String text = MessageExtractor.getTextFromPart(currentlyDecrypringOrVerifyingPart);
out.write(text.getBytes());
} else {
Log.wtf(K9.LOG_TAG, "No suitable data to stream found!");
}
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Exception while writing message to crypto provider", e);
@ -251,16 +286,16 @@ class MessageCryptoHelper {
private PipedOutputStream getPipedOutputStreamForDecryptedData(final CountDownLatch latch) throws IOException {
PipedOutputStream decryptedOutputStream = new PipedOutputStream();
final PipedInputStream decryptedInputStream = new PipedInputStream(decryptedOutputStream);
new AsyncTask<Void, Void, OpenPgpResultBodyPart>() {
new AsyncTask<Void, Void, MimeBodyPart>() {
@Override
protected OpenPgpResultBodyPart doInBackground(Void... params) {
OpenPgpResultBodyPart decryptedPart = null;
protected MimeBodyPart doInBackground(Void... params) {
MimeBodyPart decryptedPart = null;
try {
decryptedPart = DecryptStreamParser.parse(context, decryptedInputStream);
latch.await();
} catch (InterruptedException e) {
Log.e(K9.LOG_TAG, "we were interrupted while waiting for onReturn!", e);
Log.w(K9.LOG_TAG, "we were interrupted while waiting for onReturn!", e);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Something went wrong while parsing the decrypted MIME part", e);
//TODO: pass error to main thread and display error message to user
@ -269,14 +304,14 @@ class MessageCryptoHelper {
}
@Override
protected void onPostExecute(OpenPgpResultBodyPart decryptedPart) {
protected void onPostExecute(MimeBodyPart decryptedPart) {
onCryptoConverge(decryptedPart);
}
}.execute();
return decryptedOutputStream;
}
private void onCryptoConverge(OpenPgpResultBodyPart openPgpResultBodyPart) {
private void onCryptoConverge(MimeBodyPart outputPart) {
try {
if (currentCryptoResult == null) {
Log.e(K9.LOG_TAG, "Internal error: we should have a result here!");
@ -318,24 +353,26 @@ class MessageCryptoHelper {
break;
}
case OpenPgpApi.RESULT_CODE_SUCCESS: {
if (openPgpResultBodyPart == null) {
openPgpResultBodyPart = new OpenPgpResultBodyPart(false);
}
OpenPgpResultAnnotation resultAnnotation = new OpenPgpResultAnnotation();
resultAnnotation.setOutputData(outputPart);
// TODO if the data /was/ encrypted, we should set it here!
// this is not easy to determine for inline data though
resultAnnotation.setWasEncrypted(false);
OpenPgpSignatureResult signatureResult =
currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
openPgpResultBodyPart.setSignatureResult(signatureResult);
resultAnnotation.setSignatureResult(signatureResult);
PendingIntent pendingIntent =
currentCryptoResult.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
openPgpResultBodyPart.setPendingIntent(pendingIntent);
resultAnnotation.setPendingIntent(pendingIntent);
onCryptoSuccess(openPgpResultBodyPart);
onCryptoSuccess(resultAnnotation);
break;
}
}
} catch (MessagingException e) {
// catching the empty OpenPgpResultBodyPart constructor above - this can't actually happen
Log.e(K9.LOG_TAG, "This shouldn't happen", e);
} finally {
currentCryptoResult = null;
}
@ -350,28 +387,19 @@ class MessageCryptoHelper {
}
}
private void onCryptoSuccess(OpenPgpResultBodyPart decryptedPart) {
addOpenPgpResultPartToMessage(decryptedPart);
private void onCryptoSuccess(OpenPgpResultAnnotation resultAnnotation) {
addOpenPgpResultPartToMessage(resultAnnotation);
onCryptoFinished();
}
private void addOpenPgpResultPartToMessage(OpenPgpResultBodyPart decryptedPart) {
if ( ! (currentlyDecrypringOrVerifyingPart.getBody() instanceof Multipart)) {
// TODO this is a text/plain part - care about this later!
return;
}
Multipart multipart = (Multipart) currentlyDecrypringOrVerifyingPart.getBody();
multipart.addBodyPart(decryptedPart);
private void addOpenPgpResultPartToMessage(OpenPgpResultAnnotation resultAnnotation) {
messageAnnotations.put(currentlyDecrypringOrVerifyingPart, resultAnnotation);
}
private void onCryptoFailed(OpenPgpError error) {
try {
OpenPgpResultBodyPart errorPart = new OpenPgpResultBodyPart(false);
errorPart.setError(error);
addOpenPgpResultPartToMessage(errorPart);
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "This shouldn't happen", e);
}
OpenPgpResultAnnotation errorPart = new OpenPgpResultAnnotation();
errorPart.setError(error);
addOpenPgpResultPartToMessage(errorPart);
onCryptoFinished();
}
@ -381,7 +409,25 @@ class MessageCryptoHelper {
}
private void returnResultToFragment() {
fragment.startExtractingTextAndAttachments(message);
fragment.startExtractingTextAndAttachments(messageAnnotations);
}
public static class MessageCryptoAnnotations {
private HashMap<Part,OpenPgpResultAnnotation> annotations = new HashMap<Part,OpenPgpResultAnnotation>();
private void put(Part part, OpenPgpResultAnnotation annotation) {
annotations.put(part, annotation);
}
public OpenPgpResultAnnotation get(Part part) {
return annotations.get(part);
}
public boolean has(Part part) {
return annotations.containsKey(part);
}
}
}

View File

@ -48,6 +48,7 @@ import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.MessageViewInfo;
import com.fsck.k9.ui.message.DecodeMessageLoader;
import com.fsck.k9.ui.message.LocalMessageLoader;
import com.fsck.k9.ui.messageview.MessageCryptoHelper.MessageCryptoAnnotations;
import com.fsck.k9.view.MessageHeader;
public class MessageViewFragment extends Fragment implements ConfirmationDialogFragmentListener,
@ -80,6 +81,7 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
private Account mAccount;
private MessageReference mMessageReference;
private LocalMessage mMessage;
private MessageCryptoAnnotations messageAnnotations;
private MessagingController mController;
private LayoutInflater mLayoutInflater;
private Handler handler = new Handler();
@ -258,7 +260,8 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
Toast.makeText(mContext, errorMessage, Toast.LENGTH_LONG).show();
}
void startExtractingTextAndAttachments(LocalMessage message) {
void startExtractingTextAndAttachments(MessageCryptoAnnotations annotations) {
this.messageAnnotations = annotations;
getLoaderManager().initLoader(DECODE_MESSAGE_LOADER_ID, null, decodeMessageLoaderCallback);
}
@ -742,7 +745,7 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
@Override
public Loader<MessageViewInfo> onCreateLoader(int id, Bundle args) {
setProgress(true);
return new DecodeMessageLoader(mContext, mMessage);
return new DecodeMessageLoader(mContext, mMessage, messageAnnotations);
}
@Override