1
0
mirror of https://github.com/moparisthebest/k-9 synced 2025-02-07 10:40:11 -05:00

Write decrypted bodies to temporary files

Use FileProvider to be able to open decrypted attachments
This commit is contained in:
cketti 2015-01-30 11:22:44 +01:00
parent e8c591e6be
commit de8da4dab4
8 changed files with 193 additions and 27 deletions

View File

@ -415,5 +415,17 @@
android:authorities="com.fsck.k9.provider.email" android:authorities="com.fsck.k9.provider.email"
android:exported="false"/> android:exported="false"/>
<provider
android:name=".provider.K9FileProvider"
android:authorities="com.fsck.k9.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/allowed_file_provider_paths" />
</provider>
</application> </application>
</manifest> </manifest>

View File

@ -0,0 +1,70 @@
package com.fsck.k9.crypto;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.RawDataBody;
import com.fsck.k9.mail.internet.SizeAware;
import org.apache.commons.io.IOUtils;
public class DecryptedTempFileBody implements RawDataBody, SizeAware {
private final File tempDirectory;
private final String encoding;
private File file;
public DecryptedTempFileBody(String encoding, File tempDirectory) {
this.encoding = encoding;
this.tempDirectory = tempDirectory;
}
@Override
public String getEncoding() {
return encoding;
}
@Override
public void setEncoding(String encoding) throws MessagingException {
throw new RuntimeException("Not supported");
}
public OutputStream getOutputStream() throws IOException {
file = File.createTempFile("decrypted", null, tempDirectory);
return new FileOutputStream(file);
}
@Override
public InputStream getInputStream() throws MessagingException {
try {
return new FileInputStream(file);
} catch (IOException ioe) {
throw new MessagingException("Unable to open body", ioe);
}
}
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
InputStream in = getInputStream();
try {
IOUtils.copy(in, out);
} finally {
in.close();
}
}
@Override
public long getSize() {
return file.length();
}
public File getFile() {
return file;
}
}

View File

@ -2,11 +2,17 @@ package com.fsck.k9.mailstore;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.Stack; import java.util.Stack;
import android.content.Context;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.crypto.DecryptedTempFileBody;
import com.fsck.k9.mail.Body; import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart; import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
@ -19,16 +25,24 @@ import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.MimeUtility;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.MimeException; import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.codec.Base64InputStream;
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
import org.apache.james.mime4j.io.EOLConvertingInputStream; import org.apache.james.mime4j.io.EOLConvertingInputStream;
import org.apache.james.mime4j.parser.ContentHandler; import org.apache.james.mime4j.parser.ContentHandler;
import org.apache.james.mime4j.parser.MimeStreamParser; import org.apache.james.mime4j.parser.MimeStreamParser;
import org.apache.james.mime4j.stream.BodyDescriptor; import org.apache.james.mime4j.stream.BodyDescriptor;
import org.apache.james.mime4j.stream.Field; import org.apache.james.mime4j.stream.Field;
import org.apache.james.mime4j.stream.MimeConfig; import org.apache.james.mime4j.stream.MimeConfig;
import org.apache.james.mime4j.util.MimeUtil;
public class DecryptStreamParser { public class DecryptStreamParser {
public static OpenPgpResultBodyPart parse(InputStream inputStream) throws MessagingException, IOException {
private static final String DECRYPTED_CACHE_DIRECTORY = "decrypted";
public static OpenPgpResultBodyPart parse(Context context, InputStream inputStream) throws MessagingException, IOException {
File decryptedTempDirectory = getDecryptedTempDirectory(context);
OpenPgpResultBodyPart decryptedRootPart = new OpenPgpResultBodyPart(true); OpenPgpResultBodyPart decryptedRootPart = new OpenPgpResultBodyPart(true);
MimeConfig parserConfig = new MimeConfig(); MimeConfig parserConfig = new MimeConfig();
@ -37,7 +51,7 @@ public class DecryptStreamParser {
parserConfig.setMaxHeaderCount(-1); parserConfig.setMaxHeaderCount(-1);
MimeStreamParser parser = new MimeStreamParser(parserConfig); MimeStreamParser parser = new MimeStreamParser(parserConfig);
parser.setContentHandler(new PartBuilder(decryptedRootPart)); parser.setContentHandler(new PartBuilder(decryptedTempDirectory, decryptedRootPart));
parser.setRecurse(); parser.setRecurse();
inputStream = new BufferedInputStream(inputStream, 4096); inputStream = new BufferedInputStream(inputStream, 4096);
@ -51,26 +65,58 @@ public class DecryptStreamParser {
return decryptedRootPart; return decryptedRootPart;
} }
private static Body createBody(InputStream inputStream, String transferEncoding) throws IOException { private static Body createBody(InputStream inputStream, String transferEncoding, File decryptedTempDirectory)
//TODO: only read parts we're going to display into memory throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); DecryptedTempFileBody body = new DecryptedTempFileBody(transferEncoding, decryptedTempDirectory);
OutputStream outputStream = body.getOutputStream();
try { try {
IOUtils.copy(inputStream, byteArrayOutputStream); InputStream decodingInputStream;
} finally { boolean closeStream;
byteArrayOutputStream.close(); if (MimeUtil.ENC_QUOTED_PRINTABLE.equals(transferEncoding)) {
decodingInputStream = new QuotedPrintableInputStream(inputStream, false);
closeStream = true;
} else if (MimeUtil.ENC_BASE64.equals(transferEncoding)) {
decodingInputStream = new Base64InputStream(inputStream);
closeStream = true;
} else {
decodingInputStream = inputStream;
closeStream = false;
} }
byte[] data = byteArrayOutputStream.toByteArray(); try {
IOUtils.copy(decodingInputStream, outputStream);
} finally {
if (closeStream) {
decodingInputStream.close();
}
}
} finally {
outputStream.close();
}
return new BinaryMemoryBody(data, transferEncoding); return body;
}
private static File getDecryptedTempDirectory(Context context) {
File directory = new File(context.getCacheDir(), DECRYPTED_CACHE_DIRECTORY);
if (!directory.exists()) {
if (!directory.mkdir()) {
Log.e(K9.LOG_TAG, "Error creating directory: " + directory.getAbsolutePath());
}
}
return directory;
} }
private static class PartBuilder implements ContentHandler { private static class PartBuilder implements ContentHandler {
private final File decryptedTempDirectory;
private final OpenPgpResultBodyPart decryptedRootPart; private final OpenPgpResultBodyPart decryptedRootPart;
private final Stack<Object> stack = new Stack<Object>(); private final Stack<Object> stack = new Stack<Object>();
public PartBuilder(OpenPgpResultBodyPart decryptedRootPart) throws MessagingException { public PartBuilder(File decryptedTempDirectory, OpenPgpResultBodyPart decryptedRootPart)
throws MessagingException {
this.decryptedTempDirectory = decryptedTempDirectory;
this.decryptedRootPart = decryptedRootPart; this.decryptedRootPart = decryptedRootPart;
} }
@ -172,7 +218,7 @@ public class DecryptStreamParser {
Part part = (Part) stack.peek(); Part part = (Part) stack.peek();
String transferEncoding = bd.getTransferEncoding(); String transferEncoding = bd.getTransferEncoding();
Body body = createBody(inputStream, transferEncoding); Body body = createBody(inputStream, transferEncoding, decryptedTempDirectory);
part.setBody(body); part.setBody(body);
} }
@ -182,5 +228,4 @@ public class DecryptStreamParser {
throw new IllegalStateException("Not implemented"); throw new IllegalStateException("Not implemented");
} }
} }
} }

View File

@ -5,6 +5,7 @@ import android.content.Context;
import android.net.Uri; import android.net.Uri;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.crypto.DecryptedTempFileBody;
import com.fsck.k9.crypto.MessageDecryptVerifier; import com.fsck.k9.crypto.MessageDecryptVerifier;
import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body; import com.fsck.k9.mail.Body;
@ -20,9 +21,11 @@ import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.Viewable; import com.fsck.k9.mail.internet.Viewable;
import com.fsck.k9.mailstore.MessageViewInfo.MessageViewContainer; import com.fsck.k9.mailstore.MessageViewInfo.MessageViewContainer;
import com.fsck.k9.provider.AttachmentProvider; import com.fsck.k9.provider.AttachmentProvider;
import com.fsck.k9.provider.K9FileProvider;
import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -435,7 +438,7 @@ public class LocalMessageExtractor {
// 3. parse viewables into html string // 3. parse viewables into html string
ViewableContainer viewable = LocalMessageExtractor.extractTextAndAttachments(context, viewables, ViewableContainer viewable = LocalMessageExtractor.extractTextAndAttachments(context, viewables,
attachments); attachments);
List<AttachmentViewInfo> attachmentInfos = extractAttachmentInfos(attachments); List<AttachmentViewInfo> attachmentInfos = extractAttachmentInfos(context, attachments);
OpenPgpResultBodyPart resultBodyPart = getSignatureResultForPart(part); OpenPgpResultBodyPart resultBodyPart = getSignatureResultForPart(part);
if (resultBodyPart != NO_SIGNATURE_RESULT) { if (resultBodyPart != NO_SIGNATURE_RESULT) {
@ -522,18 +525,18 @@ public class LocalMessageExtractor {
return NO_SIGNATURE_RESULT; return NO_SIGNATURE_RESULT;
} }
private static List<AttachmentViewInfo> extractAttachmentInfos(List<Part> attachmentParts) private static List<AttachmentViewInfo> extractAttachmentInfos(Context context, List<Part> attachmentParts)
throws MessagingException { throws MessagingException {
List<AttachmentViewInfo> attachments = new ArrayList<AttachmentViewInfo>(); List<AttachmentViewInfo> attachments = new ArrayList<AttachmentViewInfo>();
for (Part part : attachmentParts) { for (Part part : attachmentParts) {
attachments.add(extractAttachmentInfo(part)); attachments.add(extractAttachmentInfo(context, part));
} }
return attachments; return attachments;
} }
private static AttachmentViewInfo extractAttachmentInfo(Part part) throws MessagingException { private static AttachmentViewInfo extractAttachmentInfo(Context context, Part part) throws MessagingException {
if (part instanceof LocalPart) { if (part instanceof LocalPart) {
LocalPart localPart = (LocalPart) part; LocalPart localPart = (LocalPart) part;
String accountUuid = localPart.getAccountUuid(); String accountUuid = localPart.getAccountUuid();
@ -546,8 +549,15 @@ public class LocalMessageExtractor {
return new AttachmentViewInfo(mimeType, displayName, size, uri, firstClassAttachment, part); return new AttachmentViewInfo(mimeType, displayName, size, uri, firstClassAttachment, part);
} else { } else {
//FIXME: The content provider URI thing needs to be reworked Body body = part.getBody();
return extractAttachmentInfo(part, null); if (body instanceof DecryptedTempFileBody) {
DecryptedTempFileBody decryptedTempFileBody = (DecryptedTempFileBody) body;
File file = decryptedTempFileBody.getFile();
Uri uri = K9FileProvider.getUriForFile(context, file, part.getMimeType());
return extractAttachmentInfo(part, uri);
} else {
throw new RuntimeException("Not supported");
}
} }
} }

View File

@ -0,0 +1,23 @@
package com.fsck.k9.provider;
import java.io.File;
import android.content.Context;
import android.net.Uri;
import android.support.v4.content.FileProvider;
public class K9FileProvider extends FileProvider {
private static final String AUTHORITY = "com.fsck.k9.fileprovider";
public static Uri getUriForFile(Context context, File file, String mimeType) {
Uri uri = FileProvider.getUriForFile(context, AUTHORITY, file);
return uri.buildUpon().appendQueryParameter("mime_type", mimeType).build();
}
@Override
public String getType(Uri uri) {
return uri.getQueryParameter("mime_type");
}
}

View File

@ -11,6 +11,7 @@ import java.util.concurrent.CountDownLatch;
import android.app.Activity; import android.app.Activity;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentSender.SendIntentException; import android.content.IntentSender.SendIntentException;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -40,10 +41,11 @@ import org.openintents.openpgp.util.OpenPgpServiceConnection;
import org.openintents.openpgp.util.OpenPgpServiceConnection.OnBound; import org.openintents.openpgp.util.OpenPgpServiceConnection.OnBound;
public class MessageCryptoHelper { class MessageCryptoHelper {
private MessageViewFragment fragment; private final Context context;
private Account account; private final MessageViewFragment fragment;
private final Account account;
private LocalMessage message; private LocalMessage message;
private Deque<Part> partsToDecryptOrVerify; private Deque<Part> partsToDecryptOrVerify;
@ -53,12 +55,13 @@ public class MessageCryptoHelper {
private static final int INVALID_OPENPGP_RESULT_CODE = -1; private static final int INVALID_OPENPGP_RESULT_CODE = -1;
MessageCryptoHelper(MessageViewFragment fragment, Account account) { public MessageCryptoHelper(Context context, MessageViewFragment fragment, Account account) {
this.context = context;
this.fragment = fragment; this.fragment = fragment;
this.account = account; this.account = account;
} }
void decryptOrVerifyMessagePartsIfNecessary(LocalMessage message) { public void decryptOrVerifyMessagePartsIfNecessary(LocalMessage message) {
this.message = message; this.message = message;
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message); List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message);
@ -242,7 +245,7 @@ public class MessageCryptoHelper {
protected OpenPgpResultBodyPart doInBackground(Void... params) { protected OpenPgpResultBodyPart doInBackground(Void... params) {
OpenPgpResultBodyPart decryptedPart = null; OpenPgpResultBodyPart decryptedPart = null;
try { try {
decryptedPart = DecryptStreamParser.parse(decryptedInputStream); decryptedPart = DecryptStreamParser.parse(context, decryptedInputStream);
latch.await(); latch.await();
} catch (InterruptedException e) { } catch (InterruptedException e) {

View File

@ -195,7 +195,7 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
Context appContext = getActivity().getApplicationContext(); Context appContext = getActivity().getApplicationContext();
mAccount = Preferences.getPreferences(appContext).getAccount(mMessageReference.accountUuid); mAccount = Preferences.getPreferences(appContext).getAccount(mMessageReference.accountUuid);
messageCryptoHelper = new MessageCryptoHelper(this, mAccount); messageCryptoHelper = new MessageCryptoHelper(mContext, this, mAccount);
if (resetPgpData) { if (resetPgpData) {
// start with fresh, empty PGP data // start with fresh, empty PGP data

View File

@ -0,0 +1,3 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="decrypted" path="decrypted" />
</paths>