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: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>
|
||||||
|
@ -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.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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.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) {
|
||||||
|
@ -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
|
||||||
|
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