aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVincent Breitmoser <valodim@mugenguild.com>2015-09-15 03:02:05 +0200
committerVincent Breitmoser <valodim@mugenguild.com>2015-09-15 03:02:05 +0200
commit3cd54581c33b20a9bfa55f767b245fc6e56e83ef (patch)
tree21719051a67fde85715640c3af8ceaea0d413694
parent3814ae7d53a22ba89f1e39d7a4661016f76cf8c8 (diff)
downloadopen-keychain-3cd54581c33b20a9bfa55f767b245fc6e56e83ef.tar.gz
open-keychain-3cd54581c33b20a9bfa55f767b245fc6e56e83ef.tar.bz2
open-keychain-3cd54581c33b20a9bfa55f767b245fc6e56e83ef.zip
mime: create more general InputDataOperation, which for now and does basic mime parsing
-rw-r--r--OpenKeychain/build.gradle1
-rw-r--r--OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/SymmetricTextOperationTests.java2
-rw-r--r--OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java173
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/MimeParsingOperation.java360
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/MimeParsingResult.java)34
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java9
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java10
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java22
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/InputDataParcel.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/MimeParsingParcel.java)39
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java31
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java3
-rw-r--r--OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java166
13 files changed, 431 insertions, 423 deletions
diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle
index ab640b1ca..c8b095eac 100644
--- a/OpenKeychain/build.gradle
+++ b/OpenKeychain/build.gradle
@@ -20,6 +20,7 @@ dependencies {
// http://www.vogella.com/tutorials/Robolectric/article.html
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.0'
+ testCompile 'org.mockito:mockito-core:1.+'
// UI testing with Espresso
androidTestCompile 'com.android.support.test:runner:0.3'
diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/SymmetricTextOperationTests.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/SymmetricTextOperationTests.java
index 3a34f15be..498df7299 100644
--- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/SymmetricTextOperationTests.java
+++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/SymmetricTextOperationTests.java
@@ -133,7 +133,7 @@ public class SymmetricTextOperationTests {
hasExtra(equalTo(Intent.EXTRA_INTENT), allOf(
hasAction(Intent.ACTION_VIEW),
hasFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION),
- hasData(allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.CONTENT_AUTHORITY))),
+ hasData(allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.AUTHORITY))),
hasType("text/plain")
))
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
diff --git a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java
index 1e6a3f69e..63c7dc6de 100644
--- a/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java
+++ b/OpenKeychain/src/androidTest/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareTest.java
@@ -96,7 +96,7 @@ public class ViewKeyAdvShareTest {
hasType("text/plain"),
hasExtra(is(Intent.EXTRA_TEXT), is("openpgp4fpr:c619d53f7a5f96f391a84ca79d604d2f310716a3")),
hasExtra(is(Intent.EXTRA_STREAM),
- allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.CONTENT_AUTHORITY)))
+ allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.AUTHORITY)))
))
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
onView(withId(R.id.view_key_action_fingerprint_share)).perform(click());
@@ -113,7 +113,7 @@ public class ViewKeyAdvShareTest {
hasType("text/plain"),
hasExtra(is(Intent.EXTRA_TEXT), startsWith("----")),
hasExtra(is(Intent.EXTRA_STREAM),
- allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.CONTENT_AUTHORITY)))
+ allOf(hasScheme("content"), hasHost(TemporaryStorageProvider.AUTHORITY)))
))
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
onView(withId(R.id.view_key_action_key_share)).perform(click());
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java
new file mode 100644
index 000000000..030c0a285
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.operations;
+
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+
+import org.apache.james.mime4j.MimeException;
+import org.apache.james.mime4j.codec.DecodeMonitor;
+import org.apache.james.mime4j.dom.FieldParser;
+import org.apache.james.mime4j.dom.field.ContentDispositionField;
+import org.apache.james.mime4j.field.DefaultFieldParser;
+import org.apache.james.mime4j.parser.AbstractContentHandler;
+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.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
+import org.sufficientlysecure.keychain.operations.results.InputDataResult;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
+import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
+import org.sufficientlysecure.keychain.pgp.Progressable;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.service.InputDataParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+import org.sufficientlysecure.keychain.util.Log;
+
+
+/** This operation deals with input data, trying to determine its type as it goes. */
+public class InputDataOperation extends BaseOperation<InputDataParcel> {
+
+ final private byte[] buf = new byte[256];
+
+ public InputDataOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
+ super(context, providerHelper, progressable);
+ }
+
+ @NonNull
+ @Override
+ public InputDataResult execute(InputDataParcel input,
+ CryptoInputParcel cryptoInput) {
+
+ final OperationLog log = new OperationLog();
+
+ log.add(LogType.MSG_MIME_PARSING, 0);
+
+ Uri currentUri;
+
+ PgpDecryptVerifyInputParcel decryptInput = input.getDecryptInput();
+ if (decryptInput != null) {
+
+ PgpDecryptVerifyOperation op =
+ new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable);
+
+ decryptInput.setInputUri(input.getInputUri());
+
+ currentUri = TemporaryStorageProvider.createFile(mContext);
+ decryptInput.setOutputUri(currentUri);
+
+ DecryptVerifyResult result = op.execute(decryptInput, cryptoInput);
+ if (result.isPending()) {
+ return new InputDataResult(log, result);
+ }
+
+ } else {
+ currentUri = input.getInputUri();
+ }
+
+ // If we aren't supposed to attempt mime decode, we are done here
+ if (!input.getMimeDecode()) {
+
+ ArrayList<Uri> uris = new ArrayList<>();
+ uris.add(currentUri);
+ return new InputDataResult(InputDataResult.RESULT_OK, log, uris);
+
+ }
+
+ try {
+ InputStream in = mContext.getContentResolver().openInputStream(currentUri);
+
+ MimeStreamParser parser = new MimeStreamParser((MimeConfig) null);
+
+ final ArrayList<Uri> outputUris = new ArrayList<>();
+
+ parser.setContentDecoding(true);
+ parser.setRecurse();
+ parser.setContentHandler(new AbstractContentHandler() {
+ String mFilename;
+
+ @Override
+ public void startHeader() throws MimeException {
+ mFilename = null;
+ }
+
+ @Override
+ public void field(Field field) throws MimeException {
+ field = DefaultFieldParser.getParser().parse(field, DecodeMonitor.SILENT);
+ if (field instanceof ContentDispositionField) {
+ mFilename = ((ContentDispositionField) field).getFilename();
+ }
+ }
+
+ @Override
+ public void body(BodyDescriptor bd, InputStream is) throws MimeException, IOException {
+
+ // log.add(LogType.MSG_MIME_PART, 0, bd.getMimeType());
+
+ Uri uri = TemporaryStorageProvider.createFile(mContext, mFilename, bd.getMimeType());
+ OutputStream out = mContext.getContentResolver().openOutputStream(uri, "w");
+
+ if (out == null) {
+ Log.e(Constants.TAG, "error!");
+ return;
+ }
+
+ int len;
+ while ( (len = is.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+
+ out.close();
+ outputUris.add(uri);
+
+ }
+ });
+
+ parser.parse(in);
+
+ log.add(LogType.MSG_MIME_PARSING_SUCCESS, 1);
+
+ return new InputDataResult(InputDataResult.RESULT_OK, log, outputUris);
+
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ return new InputDataResult(InputDataResult.RESULT_ERROR, log, null);
+ } catch (MimeException e) {
+ e.printStackTrace();
+ return new InputDataResult(InputDataResult.RESULT_ERROR, log, null);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return new InputDataResult(InputDataResult.RESULT_ERROR, log, null);
+ }
+
+ }
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/MimeParsingOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/MimeParsingOperation.java
deleted file mode 100644
index c7ebbf5fd..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/MimeParsingOperation.java
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package org.sufficientlysecure.keychain.operations;
-
-import android.content.Context;
-import android.net.Uri;
-import android.support.annotation.NonNull;
-
-import org.apache.james.mime4j.dom.BinaryBody;
-import org.apache.james.mime4j.dom.Body;
-import org.apache.james.mime4j.dom.Entity;
-import org.apache.james.mime4j.dom.Message;
-import org.apache.james.mime4j.dom.MessageBuilder;
-import org.apache.james.mime4j.dom.Multipart;
-import org.apache.james.mime4j.dom.TextBody;
-import org.apache.james.mime4j.dom.address.Mailbox;
-import org.apache.james.mime4j.dom.address.MailboxList;
-import org.apache.james.mime4j.dom.field.AddressListField;
-import org.apache.james.mime4j.dom.field.ContentTypeField;
-import org.apache.james.mime4j.dom.field.DateTimeField;
-import org.apache.james.mime4j.dom.field.UnstructuredField;
-import org.apache.james.mime4j.field.address.AddressFormatter;
-import org.apache.james.mime4j.message.BodyPart;
-import org.apache.james.mime4j.message.DefaultMessageBuilder;
-import org.apache.james.mime4j.message.MessageImpl;
-import org.apache.james.mime4j.stream.Field;
-import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.operations.results.MimeParsingResult;
-import org.sufficientlysecure.keychain.operations.results.OperationResult;
-import org.sufficientlysecure.keychain.pgp.Progressable;
-import org.sufficientlysecure.keychain.provider.ProviderHelper;
-import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
-import org.sufficientlysecure.keychain.service.MimeParsingParcel;
-import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
-import org.sufficientlysecure.keychain.util.Log;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.Reader;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Map;
-
-public class MimeParsingOperation extends BaseOperation<MimeParsingParcel> {
-
- public ArrayList<Uri> mTempUris;
-
- public MimeParsingOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
- super(context, providerHelper, progressable);
- }
-
- @NonNull
- @Override
- public MimeParsingResult execute(MimeParsingParcel parcel,
- CryptoInputParcel cryptoInputParcel) {
- OperationResult.OperationLog log = new OperationResult.OperationLog();
-
- log.add(OperationResult.LogType.MSG_MIME_PARSING, 0);
-
- mTempUris = new ArrayList<>();
-
- try {
- InputStream in = mContext.getContentResolver().openInputStream(parcel.getInputUri());
-
- final MessageBuilder builder = new DefaultMessageBuilder();
- final Message message = builder.parseMessage(in);
-
- SimpleTreeNode root = createNode(message);
-
- traverseTree(root);
-
- log.add(OperationResult.LogType.MSG_MIME_PARSING_SUCCESS, 1);
-
- } catch (Exception e) {
- Log.e(Constants.TAG, "Mime parsing error", e);
- log.add(OperationResult.LogType.MSG_MIME_PARSING_ERROR, 1);
- }
-
- return new MimeParsingResult(MimeParsingResult.RESULT_OK, log,
- mTempUris);
- }
-
- private void traverseTree(SimpleTreeNode node) {
- if (node.isLeaf()) {
- parseAndSaveAsUris(node);
- return;
- }
-
- for (SimpleTreeNode child : node.children) {
- traverseTree(child);
- }
- }
-
-
- /**
- * Wraps an Object and associates it with a text. All message parts
- * (headers, bodies, multiparts, body parts) will be wrapped in
- * ObjectWrapper instances before they are added to the JTree instance.
- */
- public static class ObjectWrapper {
- private String text = "";
- private Object object = null;
-
- public ObjectWrapper(String text, Object object) {
- this.text = text;
- this.object = object;
- }
-
- @Override
- public String toString() {
- return text;
- }
-
- public Object getObject() {
- return object;
- }
- }
-
-// /**
-// * Create a node given a Multipart body.
-// * Add the Preamble, all Body parts and the Epilogue to the node.
-// *
-// * @return the root node of the tree.
-// */
-// private DefaultMutableTreeNode createNode(Header header) {
-// DefaultMutableTreeNode node = new DefaultMutableTreeNode(
-// new ObjectWrapper("Header", header));
-//
-// for (Field field : header.getFields()) {
-// String name = field.getName();
-//
-// node.add(new DefaultMutableTreeNode(new ObjectWrapper(name, field)));
-// }
-//
-// return node;
-// }
-
- /**
- * Create a node given a Multipart body.
- * Add the Preamble, all Body parts and the Epilogue to the node.
- *
- * @param multipart the Multipart.
- * @return the root node of the tree.
- */
- private SimpleTreeNode createNode(Multipart multipart) {
- SimpleTreeNode node = new SimpleTreeNode(
- new ObjectWrapper("Multipart", multipart));
-
-// node.add(new DefaultMutableTreeNode(
-// new ObjectWrapper("Preamble", multipart.getPreamble())));
- for (Entity part : multipart.getBodyParts()) {
- node.add(createNode(part));
- }
-// node.add(new DefaultMutableTreeNode(
-// new ObjectWrapper("Epilogue", multipart.getEpilogue())));
-
- return node;
- }
-
- /**
- * Creates the tree nodes given a MIME entity (either a Message or
- * a BodyPart).
- *
- * @param entity the entity.
- * @return the root node of the tree displaying the specified entity and
- * its children.
- */
- private SimpleTreeNode createNode(Entity entity) {
-
- /*
- * Create the root node for the entity. It's either a
- * Message or a Body part.
- */
- String type = "Message";
- if (entity instanceof BodyPart) {
- type = "Body part";
- }
- SimpleTreeNode node = new SimpleTreeNode(
- new ObjectWrapper(type, entity));
-
- /*
- * Add the node encapsulating the entity Header.
- */
-// node.add(createNode(entity.getHeader()));
-
- Body body = entity.getBody();
-
- if (body instanceof Multipart) {
- /*
- * The body of the entity is a Multipart.
- */
-
- node.add(createNode((Multipart) body));
- } else if (body instanceof MessageImpl) {
- /*
- * The body is another Message.
- */
-
- node.add(createNode((MessageImpl) body));
-
- } else {
- /*
- * Discrete Body (either of type TextBody or BinaryBody).
- */
- type = "Text body";
- if (body instanceof BinaryBody) {
- type = "Binary body";
- }
-
- type += " (" + entity.getMimeType() + ")";
- node.add(new SimpleTreeNode(new ObjectWrapper(type, body)));
-
- }
-
- return node;
- }
-
- public void parseAndSaveAsUris(SimpleTreeNode node) {
- Object o = ((ObjectWrapper) node.getUserObject()).getObject();
-
- if (o instanceof TextBody) {
- /*
- * A text body. Display its contents.
- */
- TextBody body = (TextBody) o;
- StringBuilder sb = new StringBuilder();
- try {
- Reader r = body.getReader();
- int c;
- while ((c = r.read()) != -1) {
- sb.append((char) c);
- }
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- Log.d(Constants.TAG, "text: " + sb.toString());
-// textView.setText(sb.toString());
-
- Uri tempUri = null;
- try {
- tempUri = TemporaryStorageProvider.createFile(mContext, "text", "text/plain");
- OutputStream outStream = mContext.getContentResolver().openOutputStream(tempUri);
- body.writeTo(outStream);
- outStream.close();
- } catch (IOException e) {
- Log.e(Constants.TAG, "error mime parsing", e);
- }
-
- mTempUris.add(tempUri);
-
- } else if (o instanceof BinaryBody) {
- /*
- * A binary body. Display its MIME type and length in bytes.
- */
- BinaryBody body = (BinaryBody) o;
- int size = 0;
- try {
- InputStream is = body.getInputStream();
- while ((is.read()) != -1) {
- size++;
- }
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- Log.d(Constants.TAG, "Binary body\n"
- + "MIME type: "
- + body.getParent().getMimeType() + "\n"
- + "Size of decoded data: " + size + " bytes");
-
- } else if (o instanceof ContentTypeField) {
- /*
- * Content-Type field.
- */
- ContentTypeField field = (ContentTypeField) o;
- StringBuilder sb = new StringBuilder();
- sb.append("MIME type: ").append(field.getMimeType()).append("\n");
- for (Map.Entry<String, String> entry : field.getParameters().entrySet()) {
- sb.append(entry.getKey()).append(" = ").append(entry.getValue()).append("\n");
- }
- Log.d(Constants.TAG, sb.toString());
-
- } else if (o instanceof AddressListField) {
- /*
- * An address field (From, To, Cc, etc)
- */
- AddressListField field = (AddressListField) o;
- MailboxList list = field.getAddressList().flatten();
- StringBuilder sb = new StringBuilder();
- for (Mailbox mailbox : list) {
- sb.append(AddressFormatter.DEFAULT.format(mailbox, false)).append("\n");
- }
- Log.d(Constants.TAG, sb.toString());
-
- } else if (o instanceof DateTimeField) {
- Date date = ((DateTimeField) o).getDate();
- Log.d(Constants.TAG, date.toString());
- } else if (o instanceof UnstructuredField) {
- Log.d(Constants.TAG, ((UnstructuredField) o).getValue());
- } else if (o instanceof Field) {
- Log.d(Constants.TAG, ((Field) o).getBody());
- } else {
- /*
- * The Object should be a Header or a String containing a
- * Preamble or Epilogue.
- */
- Log.d(Constants.TAG, o.toString());
- }
- }
-
- public class SimpleTreeNode {
- private SimpleTreeNode parent;
- private Object userObject;
- private ArrayList<SimpleTreeNode> children;
-
- protected SimpleTreeNode(Object userObject) {
- this.parent = null;
- this.userObject = userObject;
- this.children = new ArrayList<>();
- }
-
- protected Object getUserObject() {
- return userObject;
- }
-
- protected void setUserObject(Object userObject) {
- this.userObject = userObject;
- }
-
- public void add(SimpleTreeNode newChild) {
- newChild.parent = this;
- children.add(newChild);
- }
-
- public SimpleTreeNode getParent() {
- return parent;
- }
-
- public boolean isLeaf() {
- return children.isEmpty();
- }
-
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/MimeParsingResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java
index 05f5125cb..908636ca7 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/MimeParsingResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java
@@ -22,22 +22,28 @@ import android.os.Parcel;
import java.util.ArrayList;
-public class MimeParsingResult extends OperationResult {
+public class InputDataResult extends InputPendingResult {
- public final ArrayList<Uri> mTemporaryUris;
+ public final ArrayList<Uri> mOutputUris;
+ public DecryptVerifyResult mDecryptVerifyResult;
- public ArrayList<Uri> getTemporaryUris() {
- return mTemporaryUris;
+ public InputDataResult(OperationLog log, InputPendingResult result) {
+ super(log, result);
+ mOutputUris = null;
}
- public MimeParsingResult(int result, OperationLog log, ArrayList<Uri> temporaryUris) {
+ public InputDataResult(int result, OperationLog log, ArrayList<Uri> temporaryUris) {
super(result, log);
- mTemporaryUris = temporaryUris;
+ mOutputUris = temporaryUris;
}
- protected MimeParsingResult(Parcel in) {
+ protected InputDataResult(Parcel in) {
super(in);
- mTemporaryUris = in.createTypedArrayList(Uri.CREATOR);
+ mOutputUris = in.createTypedArrayList(Uri.CREATOR);
+ }
+
+ public ArrayList<Uri> getOutputUris() {
+ return mOutputUris;
}
@Override
@@ -48,18 +54,18 @@ public class MimeParsingResult extends OperationResult {
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeTypedList(mTemporaryUris);
+ dest.writeTypedList(mOutputUris);
}
- public static final Creator<MimeParsingResult> CREATOR = new Creator<MimeParsingResult>() {
+ public static final Creator<InputDataResult> CREATOR = new Creator<InputDataResult>() {
@Override
- public MimeParsingResult createFromParcel(Parcel in) {
- return new MimeParsingResult(in);
+ public InputDataResult createFromParcel(Parcel in) {
+ return new InputDataResult(in);
}
@Override
- public MimeParsingResult[] newArray(int size) {
- return new MimeParsingResult[size];
+ public InputDataResult[] newArray(int size) {
+ return new InputDataResult[size];
}
};
} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
index d767382ae..0a8c1f653 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputPendingResult.java
@@ -38,6 +38,15 @@ public class InputPendingResult extends OperationResult {
mCryptoInputParcel = null;
}
+ public InputPendingResult(OperationLog log, InputPendingResult result) {
+ super(RESULT_PENDING, log);
+ if (!result.isPending()) {
+ throw new AssertionError("sub result must be pending!");
+ }
+ mRequiredInput = result.mRequiredInput;
+ mCryptoInputParcel = result.mCryptoInputParcel;
+ }
+
public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput,
CryptoInputParcel cryptoInputParcel) {
super(RESULT_PENDING, log);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
index a6d65688c..d56c24f91 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
@@ -86,10 +86,20 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {
return mInputBytes;
}
+ public PgpDecryptVerifyInputParcel setInputUri(Uri uri) {
+ mInputUri = uri;
+ return this;
+ }
+
Uri getInputUri() {
return mInputUri;
}
+ public PgpDecryptVerifyInputParcel setOutputUri(Uri uri) {
+ mOutputUri = uri;
+ return this;
+ }
+
Uri getOutputUri() {
return mOutputUri;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
index 7e9b24989..67f2c36bc 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryStorageProvider.java
@@ -67,8 +67,8 @@ public class TemporaryStorageProvider extends ContentProvider {
private static final String COLUMN_NAME = "name";
private static final String COLUMN_TIME = "time";
private static final String COLUMN_TYPE = "mimetype";
- public static final String CONTENT_AUTHORITY = Constants.TEMPSTORAGE_AUTHORITY;
- private static final Uri BASE_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
+ public static final String AUTHORITY = Constants.TEMPSTORAGE_AUTHORITY;
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
private static final int DB_VERSION = 3;
private static File cacheDir;
@@ -77,18 +77,18 @@ public class TemporaryStorageProvider extends ContentProvider {
ContentValues contentValues = new ContentValues();
contentValues.put(COLUMN_NAME, targetName);
contentValues.put(COLUMN_TYPE, mimeType);
- return context.getContentResolver().insert(BASE_URI, contentValues);
+ return context.getContentResolver().insert(CONTENT_URI, contentValues);
}
public static Uri createFile(Context context, String targetName) {
ContentValues contentValues = new ContentValues();
contentValues.put(COLUMN_NAME, targetName);
- return context.getContentResolver().insert(BASE_URI, contentValues);
+ return context.getContentResolver().insert(CONTENT_URI, contentValues);
}
public static Uri createFile(Context context) {
ContentValues contentValues = new ContentValues();
- return context.getContentResolver().insert(BASE_URI, contentValues);
+ return context.getContentResolver().insert(CONTENT_URI, contentValues);
}
public static int setMimeType(Context context, Uri uri, String mimetype) {
@@ -98,7 +98,7 @@ public class TemporaryStorageProvider extends ContentProvider {
}
public static int cleanUp(Context context) {
- return context.getContentResolver().delete(BASE_URI, COLUMN_TIME + "< ?",
+ return context.getContentResolver().delete(CONTENT_URI, COLUMN_TIME + "< ?",
new String[]{Long.toString(System.currentTimeMillis() - Constants.TEMPFILE_TTL)});
}
@@ -163,12 +163,19 @@ public class TemporaryStorageProvider extends ContentProvider {
throw new SecurityException("Listing temporary files is not allowed, only querying single files.");
}
+ Log.d(Constants.TAG, "being asked for file " + uri);
+
File file;
try {
file = getFile(uri);
+ if (file.exists()) {
+ Log.e(Constants.TAG, "already exists");
+ }
} catch (FileNotFoundException e) {
+ Log.e(Constants.TAG, "file not found!");
return null;
}
+
Cursor fileName = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_NAME}, COLUMN_ID + "=?",
new String[]{uri.getLastPathSegment()}, null, null, null);
if (fileName != null) {
@@ -236,7 +243,7 @@ public class TemporaryStorageProvider extends ContentProvider {
Log.e(Constants.TAG, "File creation failed!");
return null;
}
- return Uri.withAppendedPath(BASE_URI, uuid);
+ return Uri.withAppendedPath(CONTENT_URI, uuid);
}
@Override
@@ -274,6 +281,7 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ Log.d(Constants.TAG, "openFile");
return openFileHelper(uri, mode);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/MimeParsingParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/InputDataParcel.java
index ccc817c21..807577001 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/MimeParsingParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/InputDataParcel.java
@@ -21,31 +21,37 @@ import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
-public class MimeParsingParcel implements Parcelable {
+import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
+
+
+public class InputDataParcel implements Parcelable {
private Uri mInputUri;
- private Uri mOutputUri;
- public MimeParsingParcel() {
- }
+ private PgpDecryptVerifyInputParcel mDecryptInput;
+ private boolean mMimeDecode = true; // TODO default to false
- public MimeParsingParcel(Uri inputUri, Uri outputUri) {
+ public InputDataParcel(Uri inputUri, PgpDecryptVerifyInputParcel decryptInput) {
mInputUri = inputUri;
- mOutputUri = outputUri;
}
- MimeParsingParcel(Parcel source) {
+ InputDataParcel(Parcel source) {
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
mInputUri = source.readParcelable(getClass().getClassLoader());
- mOutputUri = source.readParcelable(getClass().getClassLoader());
+ mDecryptInput = source.readParcelable(getClass().getClassLoader());
+ mMimeDecode = source.readInt() != 0;
}
public Uri getInputUri() {
return mInputUri;
}
- public Uri getOutputUri() {
- return mOutputUri;
+ public PgpDecryptVerifyInputParcel getDecryptInput() {
+ return mDecryptInput;
+ }
+
+ public boolean getMimeDecode() {
+ return mMimeDecode;
}
@Override
@@ -56,16 +62,17 @@ public class MimeParsingParcel implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mInputUri, 0);
- dest.writeParcelable(mOutputUri, 0);
+ dest.writeParcelable(mDecryptInput, 0);
+ dest.writeInt(mMimeDecode ? 1 : 0);
}
- public static final Creator<MimeParsingParcel> CREATOR = new Creator<MimeParsingParcel>() {
- public MimeParsingParcel createFromParcel(final Parcel source) {
- return new MimeParsingParcel(source);
+ public static final Creator<InputDataParcel> CREATOR = new Creator<InputDataParcel>() {
+ public InputDataParcel createFromParcel(final Parcel source) {
+ return new InputDataParcel(source);
}
- public MimeParsingParcel[] newArray(final int size) {
- return new MimeParsingParcel[size];
+ public InputDataParcel[] newArray(final int size) {
+ return new InputDataParcel[size];
}
};
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
index d2128cd77..c7ac92eef 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java
@@ -36,7 +36,7 @@ import org.sufficientlysecure.keychain.operations.EditKeyOperation;
import org.sufficientlysecure.keychain.operations.ExportOperation;
import org.sufficientlysecure.keychain.operations.ImportOperation;
import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation;
-import org.sufficientlysecure.keychain.operations.MimeParsingOperation;
+import org.sufficientlysecure.keychain.operations.InputDataOperation;
import org.sufficientlysecure.keychain.operations.PromoteKeyOperation;
import org.sufficientlysecure.keychain.operations.RevokeOperation;
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
@@ -109,38 +109,29 @@ public class KeychainService extends Service implements Progressable {
// just for brevity
KeychainService outerThis = KeychainService.this;
if (inputParcel instanceof SignEncryptParcel) {
- op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis),
- outerThis, mActionCanceled);
+ op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof PgpDecryptVerifyInputParcel) {
op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof SaveKeyringParcel) {
- op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
- mActionCanceled);
+ op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof RevokeKeyringParcel) {
op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof CertifyActionsParcel) {
- op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
- mActionCanceled);
+ op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof DeleteKeyringParcel) {
op = new DeleteOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof PromoteKeyringParcel) {
- op = new PromoteKeyOperation(outerThis, new ProviderHelper(outerThis),
- outerThis, mActionCanceled);
+ op = new PromoteKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof ImportKeyringParcel) {
- op = new ImportOperation(outerThis, new ProviderHelper(outerThis), outerThis,
- mActionCanceled);
+ op = new ImportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof ExportKeyringParcel) {
- op = new ExportOperation(outerThis, new ProviderHelper(outerThis), outerThis,
- mActionCanceled);
+ op = new ExportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
} else if (inputParcel instanceof ConsolidateInputParcel) {
- op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis),
- outerThis);
+ op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else if (inputParcel instanceof KeybaseVerificationParcel) {
- op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis),
- outerThis);
- } else if (inputParcel instanceof MimeParsingParcel) {
- op = new MimeParsingOperation(outerThis, new ProviderHelper(outerThis),
- outerThis);
+ op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis), outerThis);
+ } else if (inputParcel instanceof InputDataParcel) {
+ op = new InputDataOperation(outerThis, new ProviderHelper(outerThis), outerThis);
} else {
throw new AssertionError("Unrecognized input parcel in KeychainService!");
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
index 3dda47ac5..ddaf40010 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java
@@ -58,13 +58,10 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
-import org.sufficientlysecure.keychain.operations.results.MimeParsingResult;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
// this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15)
-import org.sufficientlysecure.keychain.service.MimeParsingParcel;
-import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder;
import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel;
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java
new file mode 100644
index 000000000..71673bdb7
--- /dev/null
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.pgp;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.security.Security;
+import java.util.ArrayList;
+
+import android.app.Application;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.net.Uri;
+
+import junit.framework.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContentProvider;
+import org.robolectric.shadows.ShadowContentResolver;
+import org.robolectric.shadows.ShadowLog;
+import org.spongycastle.jce.provider.BouncyCastleProvider;
+import org.sufficientlysecure.keychain.WorkaroundBuildConfig;
+import org.sufficientlysecure.keychain.operations.InputDataOperation;
+import org.sufficientlysecure.keychain.operations.results.InputDataResult;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
+import org.sufficientlysecure.keychain.service.InputDataParcel;
+import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+@RunWith(RobolectricGradleTestRunner.class)
+@Config(constants = WorkaroundBuildConfig.class, sdk = 21, manifest = "src/main/AndroidManifest.xml")
+public class InputDataOperationTest {
+
+ static PrintStream oldShadowStream;
+
+ @BeforeClass
+ public static void setUpOnce() throws Exception {
+
+ Security.insertProviderAt(new BouncyCastleProvider(), 1);
+ oldShadowStream = ShadowLog.stream;
+ // ShadowLog.stream = System.out;
+
+ }
+
+ @Before
+ public void setUp() {
+ // don't log verbosely here, we're not here to test imports
+ ShadowLog.stream = oldShadowStream;
+
+ // ok NOW log verbosely!
+ ShadowLog.stream = System.out;
+ }
+
+ @Test
+ public void testMimeDecoding () throws Exception {
+
+ String mimeMail =
+ "Content-Type: multipart/mixed; boundary=\"=-26BafqxfXmhVNMbYdoIi\"\n" +
+ "\n" +
+ "--=-26BafqxfXmhVNMbYdoIi\n" +
+ "Content-Type: text/plain\n" +
+ "Content-Transfer-Encoding: quoted-printable\n" +
+ "Content-Disposition: attachment; filename=data.txt\n" +
+ "\n" +
+ "message part 1\n" +
+ "\n" +
+ "--=-26BafqxfXmhVNMbYdoIi\n" +
+ "Content-Type: text/testvalue\n" +
+ "Content-Description: Dummy content description\n" +
+ "\n" +
+ "message part 2.1\n" +
+ "message part 2.2\n" +
+ "\n" +
+ "--=-26BafqxfXmhVNMbYdoIi--";
+
+
+ ByteArrayOutputStream outStream1 = new ByteArrayOutputStream();
+ ByteArrayOutputStream outStream2 = new ByteArrayOutputStream();
+ ContentResolver mockResolver = mock(ContentResolver.class);
+
+ // fake openOutputStream first and second
+ when(mockResolver.openOutputStream(any(Uri.class), eq("w")))
+ .thenReturn(outStream1, outStream2);
+
+ // fake openInputStream
+ Uri fakeInputUri = Uri.parse("content://fake/1");
+ when(mockResolver.openInputStream(fakeInputUri)).thenReturn(
+ new ByteArrayInputStream(mimeMail.getBytes()));
+
+ Uri fakeOutputUri1 = Uri.parse("content://fake/out/1");
+ Uri fakeOutputUri2 = Uri.parse("content://fake/out/2");
+ when(mockResolver.insert(eq(TemporaryStorageProvider.CONTENT_URI), any(ContentValues.class)))
+ .thenReturn(fakeOutputUri1, fakeOutputUri2);
+
+ // application which returns mockresolver
+ Application spyApplication = spy(RuntimeEnvironment.application);
+ when(spyApplication.getContentResolver()).thenReturn(mockResolver);
+
+ InputDataOperation op = new InputDataOperation(spyApplication,
+ new ProviderHelper(RuntimeEnvironment.application), null);
+
+ PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel();
+ InputDataParcel input = new InputDataParcel(fakeInputUri, decryptInput);
+
+ InputDataResult result = op.execute(input, new CryptoInputParcel());
+
+ // must be successful, no verification, have two output URIs
+ Assert.assertTrue(result.success());
+ Assert.assertNull(result.mDecryptVerifyResult);
+
+ ArrayList<Uri> outUris = result.getOutputUris();
+ Assert.assertEquals("must have two output URIs", 2, outUris.size());
+ Assert.assertEquals("first uri must be the one we provided", fakeOutputUri1, outUris.get(0));
+ verify(mockResolver).openOutputStream(result.getOutputUris().get(0), "w");
+ Assert.assertEquals("second uri must be the one we provided", fakeOutputUri2, outUris.get(1));
+ verify(mockResolver).openOutputStream(result.getOutputUris().get(1), "w");
+
+ ContentValues contentValues = new ContentValues();
+ contentValues.put("name", "data.txt");
+ contentValues.put("mimetype", "text/plain");
+ verify(mockResolver).insert(TemporaryStorageProvider.CONTENT_URI, contentValues);
+ contentValues.put("name", (String) null);
+ contentValues.put("mimetype", "text/testvalue");
+ verify(mockResolver).insert(TemporaryStorageProvider.CONTENT_URI, contentValues);
+
+ // quoted-printable returns windows style line endings for some reason?
+ Assert.assertEquals("first part must have expected content",
+ "message part 1\r\n", new String(outStream1.toByteArray()));
+ Assert.assertEquals("second part must have expected content",
+ "message part 2.1\nmessage part 2.2\n", new String(outStream2.toByteArray()));
+
+
+ }
+
+}