diff --git a/k9mail/src/main/AndroidManifest.xml b/k9mail/src/main/AndroidManifest.xml index 16ce08390..acbfa12a8 100644 --- a/k9mail/src/main/AndroidManifest.xml +++ b/k9mail/src/main/AndroidManifest.xml @@ -415,5 +415,17 @@ android:authorities="com.fsck.k9.provider.email" android:exported="false"/> + + + + + + diff --git a/k9mail/src/main/java/com/fsck/k9/crypto/DecryptedTempFileBody.java b/k9mail/src/main/java/com/fsck/k9/crypto/DecryptedTempFileBody.java new file mode 100644 index 000000000..3d7e8845a --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/crypto/DecryptedTempFileBody.java @@ -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; + } +} diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/DecryptStreamParser.java b/k9mail/src/main/java/com/fsck/k9/mailstore/DecryptStreamParser.java index 0661865fd..b5f61ff95 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/DecryptStreamParser.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/DecryptStreamParser.java @@ -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); + 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; + } + + try { + IOUtils.copy(decodingInputStream, outputStream); + } finally { + if (closeStream) { + decodingInputStream.close(); + } + } } finally { - byteArrayOutputStream.close(); + outputStream.close(); } - byte[] data = byteArrayOutputStream.toByteArray(); + return body; + } - return new BinaryMemoryBody(data, transferEncoding); + 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 Stack stack = new Stack(); - 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"); } } - } diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java index 81b6feb23..d0b8a5fce 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalMessageExtractor.java @@ -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 attachmentInfos = extractAttachmentInfos(attachments); + List 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 extractAttachmentInfos(List attachmentParts) + private static List extractAttachmentInfos(Context context, List attachmentParts) throws MessagingException { List attachments = new ArrayList(); 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"); + } } } diff --git a/k9mail/src/main/java/com/fsck/k9/provider/K9FileProvider.java b/k9mail/src/main/java/com/fsck/k9/provider/K9FileProvider.java new file mode 100644 index 000000000..87269e870 --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/provider/K9FileProvider.java @@ -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"); + } +} diff --git a/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageCryptoHelper.java b/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageCryptoHelper.java index 6d579f525..9e5837a57 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageCryptoHelper.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageCryptoHelper.java @@ -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 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 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) { diff --git a/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java b/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java index fe325782f..f016d3950 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/messageview/MessageViewFragment.java @@ -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 diff --git a/k9mail/src/main/res/xml/allowed_file_provider_paths.xml b/k9mail/src/main/res/xml/allowed_file_provider_paths.xml new file mode 100644 index 000000000..84682749b --- /dev/null +++ b/k9mail/src/main/res/xml/allowed_file_provider_paths.xml @@ -0,0 +1,3 @@ + + +