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:
parent
e8c591e6be
commit
de8da4dab4
@ -415,5 +415,17 @@
|
||||
android:authorities="com.fsck.k9.provider.email"
|
||||
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>
|
||||
</manifest>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -2,11 +2,17 @@ package com.fsck.k9.mailstore;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
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.BodyPart;
|
||||
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 org.apache.commons.io.IOUtils;
|
||||
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.parser.ContentHandler;
|
||||
import org.apache.james.mime4j.parser.MimeStreamParser;
|
||||
import org.apache.james.mime4j.stream.BodyDescriptor;
|
||||
import org.apache.james.mime4j.stream.Field;
|
||||
import org.apache.james.mime4j.stream.MimeConfig;
|
||||
import org.apache.james.mime4j.util.MimeUtil;
|
||||
|
||||
|
||||
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);
|
||||
|
||||
MimeConfig parserConfig = new MimeConfig();
|
||||
@ -37,7 +51,7 @@ public class DecryptStreamParser {
|
||||
parserConfig.setMaxHeaderCount(-1);
|
||||
|
||||
MimeStreamParser parser = new MimeStreamParser(parserConfig);
|
||||
parser.setContentHandler(new PartBuilder(decryptedRootPart));
|
||||
parser.setContentHandler(new PartBuilder(decryptedTempDirectory, decryptedRootPart));
|
||||
parser.setRecurse();
|
||||
|
||||
inputStream = new BufferedInputStream(inputStream, 4096);
|
||||
@ -51,26 +65,58 @@ public class DecryptStreamParser {
|
||||
return decryptedRootPart;
|
||||
}
|
||||
|
||||
private static Body createBody(InputStream inputStream, String transferEncoding) throws IOException {
|
||||
//TODO: only read parts we're going to display into memory
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
private static Body createBody(InputStream inputStream, String transferEncoding, File decryptedTempDirectory)
|
||||
throws IOException {
|
||||
DecryptedTempFileBody body = new DecryptedTempFileBody(transferEncoding, decryptedTempDirectory);
|
||||
OutputStream outputStream = body.getOutputStream();
|
||||
try {
|
||||
IOUtils.copy(inputStream, byteArrayOutputStream);
|
||||
} finally {
|
||||
byteArrayOutputStream.close();
|
||||
InputStream decodingInputStream;
|
||||
boolean closeStream;
|
||||
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 final File decryptedTempDirectory;
|
||||
private final OpenPgpResultBodyPart decryptedRootPart;
|
||||
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;
|
||||
}
|
||||
|
||||
@ -172,7 +218,7 @@ public class DecryptStreamParser {
|
||||
Part part = (Part) stack.peek();
|
||||
|
||||
String transferEncoding = bd.getTransferEncoding();
|
||||
Body body = createBody(inputStream, transferEncoding);
|
||||
Body body = createBody(inputStream, transferEncoding, decryptedTempDirectory);
|
||||
|
||||
part.setBody(body);
|
||||
}
|
||||
@ -182,5 +228,4 @@ public class DecryptStreamParser {
|
||||
throw new IllegalStateException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import android.content.Context;
|
||||
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;
|
||||
@ -20,9 +21,11 @@ import com.fsck.k9.mail.internet.MimeUtility;
|
||||
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 org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@ -435,7 +438,7 @@ public class LocalMessageExtractor {
|
||||
// 3. parse viewables into html string
|
||||
ViewableContainer viewable = LocalMessageExtractor.extractTextAndAttachments(context, viewables,
|
||||
attachments);
|
||||
List<AttachmentViewInfo> attachmentInfos = extractAttachmentInfos(attachments);
|
||||
List<AttachmentViewInfo> attachmentInfos = extractAttachmentInfos(context, attachments);
|
||||
|
||||
OpenPgpResultBodyPart resultBodyPart = getSignatureResultForPart(part);
|
||||
if (resultBodyPart != NO_SIGNATURE_RESULT) {
|
||||
@ -522,18 +525,18 @@ public class LocalMessageExtractor {
|
||||
return NO_SIGNATURE_RESULT;
|
||||
}
|
||||
|
||||
private static List<AttachmentViewInfo> extractAttachmentInfos(List<Part> attachmentParts)
|
||||
private static List<AttachmentViewInfo> extractAttachmentInfos(Context context, List<Part> attachmentParts)
|
||||
throws MessagingException {
|
||||
|
||||
List<AttachmentViewInfo> attachments = new ArrayList<AttachmentViewInfo>();
|
||||
for (Part part : attachmentParts) {
|
||||
attachments.add(extractAttachmentInfo(part));
|
||||
attachments.add(extractAttachmentInfo(context, part));
|
||||
}
|
||||
|
||||
return attachments;
|
||||
}
|
||||
|
||||
private static AttachmentViewInfo extractAttachmentInfo(Part part) throws MessagingException {
|
||||
private static AttachmentViewInfo extractAttachmentInfo(Context context, Part part) throws MessagingException {
|
||||
if (part instanceof LocalPart) {
|
||||
LocalPart localPart = (LocalPart) part;
|
||||
String accountUuid = localPart.getAccountUuid();
|
||||
@ -546,8 +549,15 @@ public class LocalMessageExtractor {
|
||||
|
||||
return new AttachmentViewInfo(mimeType, displayName, size, uri, firstClassAttachment, part);
|
||||
} else {
|
||||
//FIXME: The content provider URI thing needs to be reworked
|
||||
return extractAttachmentInfo(part, null);
|
||||
Body body = part.getBody();
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.os.AsyncTask;
|
||||
@ -40,10 +41,11 @@ import org.openintents.openpgp.util.OpenPgpServiceConnection;
|
||||
import org.openintents.openpgp.util.OpenPgpServiceConnection.OnBound;
|
||||
|
||||
|
||||
public class MessageCryptoHelper {
|
||||
class MessageCryptoHelper {
|
||||
|
||||
private MessageViewFragment fragment;
|
||||
private Account account;
|
||||
private final Context context;
|
||||
private final MessageViewFragment fragment;
|
||||
private final Account account;
|
||||
private LocalMessage message;
|
||||
|
||||
private Deque<Part> partsToDecryptOrVerify;
|
||||
@ -53,12 +55,13 @@ public class MessageCryptoHelper {
|
||||
|
||||
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.account = account;
|
||||
}
|
||||
|
||||
void decryptOrVerifyMessagePartsIfNecessary(LocalMessage message) {
|
||||
public void decryptOrVerifyMessagePartsIfNecessary(LocalMessage message) {
|
||||
this.message = message;
|
||||
|
||||
List<Part> encryptedParts = MessageDecryptVerifier.findEncryptedParts(message);
|
||||
@ -242,7 +245,7 @@ public class MessageCryptoHelper {
|
||||
protected OpenPgpResultBodyPart doInBackground(Void... params) {
|
||||
OpenPgpResultBodyPart decryptedPart = null;
|
||||
try {
|
||||
decryptedPart = DecryptStreamParser.parse(decryptedInputStream);
|
||||
decryptedPart = DecryptStreamParser.parse(context, decryptedInputStream);
|
||||
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
|
@ -195,7 +195,7 @@ public class MessageViewFragment extends Fragment implements ConfirmationDialogF
|
||||
|
||||
Context appContext = getActivity().getApplicationContext();
|
||||
mAccount = Preferences.getPreferences(appContext).getAccount(mMessageReference.accountUuid);
|
||||
messageCryptoHelper = new MessageCryptoHelper(this, mAccount);
|
||||
messageCryptoHelper = new MessageCryptoHelper(mContext, this, mAccount);
|
||||
|
||||
if (resetPgpData) {
|
||||
// start with fresh, empty PGP data
|
||||
|
3
k9mail/src/main/res/xml/allowed_file_provider_paths.xml
Normal file
3
k9mail/src/main/res/xml/allowed_file_provider_paths.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<cache-path name="decrypted" path="decrypted" />
|
||||
</paths>
|
Loading…
Reference in New Issue
Block a user