mime: create more general InputDataOperation, which for now and does basic mime parsing
This commit is contained in:
@@ -20,6 +20,7 @@ dependencies {
|
|||||||
// http://www.vogella.com/tutorials/Robolectric/article.html
|
// http://www.vogella.com/tutorials/Robolectric/article.html
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.robolectric:robolectric:3.0'
|
testCompile 'org.robolectric:robolectric:3.0'
|
||||||
|
testCompile 'org.mockito:mockito-core:1.+'
|
||||||
|
|
||||||
// UI testing with Espresso
|
// UI testing with Espresso
|
||||||
androidTestCompile 'com.android.support.test:runner:0.3'
|
androidTestCompile 'com.android.support.test:runner:0.3'
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ public class SymmetricTextOperationTests {
|
|||||||
hasExtra(equalTo(Intent.EXTRA_INTENT), allOf(
|
hasExtra(equalTo(Intent.EXTRA_INTENT), allOf(
|
||||||
hasAction(Intent.ACTION_VIEW),
|
hasAction(Intent.ACTION_VIEW),
|
||||||
hasFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION),
|
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")
|
hasType("text/plain")
|
||||||
))
|
))
|
||||||
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
|
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public class ViewKeyAdvShareTest {
|
|||||||
hasType("text/plain"),
|
hasType("text/plain"),
|
||||||
hasExtra(is(Intent.EXTRA_TEXT), is("openpgp4fpr:c619d53f7a5f96f391a84ca79d604d2f310716a3")),
|
hasExtra(is(Intent.EXTRA_TEXT), is("openpgp4fpr:c619d53f7a5f96f391a84ca79d604d2f310716a3")),
|
||||||
hasExtra(is(Intent.EXTRA_STREAM),
|
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));
|
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
|
||||||
onView(withId(R.id.view_key_action_fingerprint_share)).perform(click());
|
onView(withId(R.id.view_key_action_fingerprint_share)).perform(click());
|
||||||
@@ -113,7 +113,7 @@ public class ViewKeyAdvShareTest {
|
|||||||
hasType("text/plain"),
|
hasType("text/plain"),
|
||||||
hasExtra(is(Intent.EXTRA_TEXT), startsWith("----")),
|
hasExtra(is(Intent.EXTRA_TEXT), startsWith("----")),
|
||||||
hasExtra(is(Intent.EXTRA_STREAM),
|
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));
|
)).respondWith(new ActivityResult(Activity.RESULT_OK, null));
|
||||||
onView(withId(R.id.view_key_action_key_share)).perform(click());
|
onView(withId(R.id.view_key_action_key_share)).perform(click());
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -22,22 +22,28 @@ import android.os.Parcel;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
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() {
|
public InputDataResult(OperationLog log, InputPendingResult result) {
|
||||||
return mTemporaryUris;
|
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);
|
super(result, log);
|
||||||
mTemporaryUris = temporaryUris;
|
mOutputUris = temporaryUris;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MimeParsingResult(Parcel in) {
|
protected InputDataResult(Parcel in) {
|
||||||
super(in);
|
super(in);
|
||||||
mTemporaryUris = in.createTypedArrayList(Uri.CREATOR);
|
mOutputUris = in.createTypedArrayList(Uri.CREATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Uri> getOutputUris() {
|
||||||
|
return mOutputUris;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -48,18 +54,18 @@ public class MimeParsingResult extends OperationResult {
|
|||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
super.writeToParcel(dest, 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
|
@Override
|
||||||
public MimeParsingResult createFromParcel(Parcel in) {
|
public InputDataResult createFromParcel(Parcel in) {
|
||||||
return new MimeParsingResult(in);
|
return new InputDataResult(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MimeParsingResult[] newArray(int size) {
|
public InputDataResult[] newArray(int size) {
|
||||||
return new MimeParsingResult[size];
|
return new InputDataResult[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,15 @@ public class InputPendingResult extends OperationResult {
|
|||||||
mCryptoInputParcel = null;
|
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,
|
public InputPendingResult(OperationLog log, RequiredInputParcel requiredInput,
|
||||||
CryptoInputParcel cryptoInputParcel) {
|
CryptoInputParcel cryptoInputParcel) {
|
||||||
super(RESULT_PENDING, log);
|
super(RESULT_PENDING, log);
|
||||||
|
|||||||
@@ -86,10 +86,20 @@ public class PgpDecryptVerifyInputParcel implements Parcelable {
|
|||||||
return mInputBytes;
|
return mInputBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel setInputUri(Uri uri) {
|
||||||
|
mInputUri = uri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
Uri getInputUri() {
|
Uri getInputUri() {
|
||||||
return mInputUri;
|
return mInputUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PgpDecryptVerifyInputParcel setOutputUri(Uri uri) {
|
||||||
|
mOutputUri = uri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
Uri getOutputUri() {
|
Uri getOutputUri() {
|
||||||
return mOutputUri;
|
return mOutputUri;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ public class TemporaryStorageProvider extends ContentProvider {
|
|||||||
private static final String COLUMN_NAME = "name";
|
private static final String COLUMN_NAME = "name";
|
||||||
private static final String COLUMN_TIME = "time";
|
private static final String COLUMN_TIME = "time";
|
||||||
private static final String COLUMN_TYPE = "mimetype";
|
private static final String COLUMN_TYPE = "mimetype";
|
||||||
public static final String CONTENT_AUTHORITY = Constants.TEMPSTORAGE_AUTHORITY;
|
public static final String AUTHORITY = Constants.TEMPSTORAGE_AUTHORITY;
|
||||||
private static final Uri BASE_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
|
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
|
||||||
private static final int DB_VERSION = 3;
|
private static final int DB_VERSION = 3;
|
||||||
|
|
||||||
private static File cacheDir;
|
private static File cacheDir;
|
||||||
@@ -77,18 +77,18 @@ public class TemporaryStorageProvider extends ContentProvider {
|
|||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
contentValues.put(COLUMN_NAME, targetName);
|
contentValues.put(COLUMN_NAME, targetName);
|
||||||
contentValues.put(COLUMN_TYPE, mimeType);
|
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) {
|
public static Uri createFile(Context context, String targetName) {
|
||||||
ContentValues contentValues = new ContentValues();
|
ContentValues contentValues = new ContentValues();
|
||||||
contentValues.put(COLUMN_NAME, targetName);
|
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) {
|
public static Uri createFile(Context context) {
|
||||||
ContentValues contentValues = new ContentValues();
|
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) {
|
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) {
|
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)});
|
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.");
|
throw new SecurityException("Listing temporary files is not allowed, only querying single files.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d(Constants.TAG, "being asked for file " + uri);
|
||||||
|
|
||||||
File file;
|
File file;
|
||||||
try {
|
try {
|
||||||
file = getFile(uri);
|
file = getFile(uri);
|
||||||
|
if (file.exists()) {
|
||||||
|
Log.e(Constants.TAG, "already exists");
|
||||||
|
}
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
|
Log.e(Constants.TAG, "file not found!");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cursor fileName = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_NAME}, COLUMN_ID + "=?",
|
Cursor fileName = db.getReadableDatabase().query(TABLE_FILES, new String[]{COLUMN_NAME}, COLUMN_ID + "=?",
|
||||||
new String[]{uri.getLastPathSegment()}, null, null, null);
|
new String[]{uri.getLastPathSegment()}, null, null, null);
|
||||||
if (fileName != null) {
|
if (fileName != null) {
|
||||||
@@ -236,7 +243,7 @@ public class TemporaryStorageProvider extends ContentProvider {
|
|||||||
Log.e(Constants.TAG, "File creation failed!");
|
Log.e(Constants.TAG, "File creation failed!");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return Uri.withAppendedPath(BASE_URI, uuid);
|
return Uri.withAppendedPath(CONTENT_URI, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -274,6 +281,7 @@ public class TemporaryStorageProvider extends ContentProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||||
|
Log.d(Constants.TAG, "openFile");
|
||||||
return openFileHelper(uri, mode);
|
return openFileHelper(uri, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,31 +21,37 @@ import android.net.Uri;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
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 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;
|
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
|
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
|
||||||
mInputUri = source.readParcelable(getClass().getClassLoader());
|
mInputUri = source.readParcelable(getClass().getClassLoader());
|
||||||
mOutputUri = source.readParcelable(getClass().getClassLoader());
|
mDecryptInput = source.readParcelable(getClass().getClassLoader());
|
||||||
|
mMimeDecode = source.readInt() != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri getInputUri() {
|
public Uri getInputUri() {
|
||||||
return mInputUri;
|
return mInputUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri getOutputUri() {
|
public PgpDecryptVerifyInputParcel getDecryptInput() {
|
||||||
return mOutputUri;
|
return mDecryptInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getMimeDecode() {
|
||||||
|
return mMimeDecode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,16 +62,17 @@ public class MimeParsingParcel implements Parcelable {
|
|||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
dest.writeParcelable(mInputUri, 0);
|
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 static final Creator<InputDataParcel> CREATOR = new Creator<InputDataParcel>() {
|
||||||
public MimeParsingParcel createFromParcel(final Parcel source) {
|
public InputDataParcel createFromParcel(final Parcel source) {
|
||||||
return new MimeParsingParcel(source);
|
return new InputDataParcel(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MimeParsingParcel[] newArray(final int size) {
|
public InputDataParcel[] newArray(final int size) {
|
||||||
return new MimeParsingParcel[size];
|
return new InputDataParcel[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ import org.sufficientlysecure.keychain.operations.EditKeyOperation;
|
|||||||
import org.sufficientlysecure.keychain.operations.ExportOperation;
|
import org.sufficientlysecure.keychain.operations.ExportOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
import org.sufficientlysecure.keychain.operations.ImportOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.KeybaseVerificationOperation;
|
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.PromoteKeyOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.RevokeOperation;
|
import org.sufficientlysecure.keychain.operations.RevokeOperation;
|
||||||
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
|
import org.sufficientlysecure.keychain.operations.SignEncryptOperation;
|
||||||
@@ -109,38 +109,29 @@ public class KeychainService extends Service implements Progressable {
|
|||||||
// just for brevity
|
// just for brevity
|
||||||
KeychainService outerThis = KeychainService.this;
|
KeychainService outerThis = KeychainService.this;
|
||||||
if (inputParcel instanceof SignEncryptParcel) {
|
if (inputParcel instanceof SignEncryptParcel) {
|
||||||
op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis),
|
op = new SignEncryptOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
|
||||||
outerThis, mActionCanceled);
|
|
||||||
} else if (inputParcel instanceof PgpDecryptVerifyInputParcel) {
|
} else if (inputParcel instanceof PgpDecryptVerifyInputParcel) {
|
||||||
op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
op = new PgpDecryptVerifyOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
||||||
} else if (inputParcel instanceof SaveKeyringParcel) {
|
} else if (inputParcel instanceof SaveKeyringParcel) {
|
||||||
op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
|
op = new EditKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
|
||||||
mActionCanceled);
|
|
||||||
} else if (inputParcel instanceof RevokeKeyringParcel) {
|
} else if (inputParcel instanceof RevokeKeyringParcel) {
|
||||||
op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
op = new RevokeOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
||||||
} else if (inputParcel instanceof CertifyActionsParcel) {
|
} else if (inputParcel instanceof CertifyActionsParcel) {
|
||||||
op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis,
|
op = new CertifyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
|
||||||
mActionCanceled);
|
|
||||||
} else if (inputParcel instanceof DeleteKeyringParcel) {
|
} else if (inputParcel instanceof DeleteKeyringParcel) {
|
||||||
op = new DeleteOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
op = new DeleteOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
||||||
} else if (inputParcel instanceof PromoteKeyringParcel) {
|
} else if (inputParcel instanceof PromoteKeyringParcel) {
|
||||||
op = new PromoteKeyOperation(outerThis, new ProviderHelper(outerThis),
|
op = new PromoteKeyOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
|
||||||
outerThis, mActionCanceled);
|
|
||||||
} else if (inputParcel instanceof ImportKeyringParcel) {
|
} else if (inputParcel instanceof ImportKeyringParcel) {
|
||||||
op = new ImportOperation(outerThis, new ProviderHelper(outerThis), outerThis,
|
op = new ImportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
|
||||||
mActionCanceled);
|
|
||||||
} else if (inputParcel instanceof ExportKeyringParcel) {
|
} else if (inputParcel instanceof ExportKeyringParcel) {
|
||||||
op = new ExportOperation(outerThis, new ProviderHelper(outerThis), outerThis,
|
op = new ExportOperation(outerThis, new ProviderHelper(outerThis), outerThis, mActionCanceled);
|
||||||
mActionCanceled);
|
|
||||||
} else if (inputParcel instanceof ConsolidateInputParcel) {
|
} else if (inputParcel instanceof ConsolidateInputParcel) {
|
||||||
op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis),
|
op = new ConsolidateOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
||||||
outerThis);
|
|
||||||
} else if (inputParcel instanceof KeybaseVerificationParcel) {
|
} else if (inputParcel instanceof KeybaseVerificationParcel) {
|
||||||
op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis),
|
op = new KeybaseVerificationOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
||||||
outerThis);
|
} else if (inputParcel instanceof InputDataParcel) {
|
||||||
} else if (inputParcel instanceof MimeParsingParcel) {
|
op = new InputDataOperation(outerThis, new ProviderHelper(outerThis), outerThis);
|
||||||
op = new MimeParsingOperation(outerThis, new ProviderHelper(outerThis),
|
|
||||||
outerThis);
|
|
||||||
} else {
|
} else {
|
||||||
throw new AssertionError("Unrecognized input parcel in KeychainService!");
|
throw new AssertionError("Unrecognized input parcel in KeychainService!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,13 +58,10 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
|
|||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
|
||||||
import org.sufficientlysecure.keychain.operations.results.MimeParsingResult;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
|
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)
|
// 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.base.QueueingCryptoOperationFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder;
|
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder;
|
||||||
import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel;
|
import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel;
|
||||||
|
|||||||
@@ -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()));
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user