From dbaf7070ead596f5c70ad48fc55aada2f77a856a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 10 Aug 2015 14:35:15 +0200 Subject: [PATCH 01/34] WIP mime parsing --- OpenKeychain/build.gradle | 10 +- .../operations/MimeParsingOperation.java | 360 ++++++++++++++++++ .../operations/results/MimeParsingResult.java | 65 ++++ .../operations/results/OperationResult.java | 5 + .../keychain/service/KeychainService.java | 4 + .../keychain/service/MimeParsingParcel.java | 73 ++++ .../keychain/ui/DecryptListFragment.java | 130 +++++-- OpenKeychain/src/main/res/values/strings.xml | 5 + 8 files changed, 610 insertions(+), 42 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/MimeParsingOperation.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/MimeParsingResult.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/MimeParsingParcel.java diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 7d2f40128..5bf8f6d1b 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -56,6 +56,8 @@ dependencies { compile 'com.mikepenz.iconics:community-material-typeface:1.0.0@aar' compile 'com.nispok:snackbar:2.11.0' compile 'com.squareup.okhttp:okhttp:2.4.0' + compile 'org.apache.james:apache-mime4j-core:0.7.2' + compile 'org.apache.james:apache-mime4j-dom:0.7.2' // libs as submodules compile project(':extern:openpgp-api-lib:openpgp-api') @@ -199,15 +201,21 @@ android { htmlOutput file('lint-report.html') } - // Disable preDexing, causes com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000) on some systems dexOptions { + // Disable preDexing, causes com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000) on some systems preDexLibraries = false + // faster with incremental? +// incremental true + javaMaxHeapSize "4g" } packagingOptions { exclude 'LICENSE.txt' exclude 'META-INF/LICENSE.txt' exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' exclude '.readme' } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/MimeParsingOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/MimeParsingOperation.java new file mode 100644 index 000000000..c7ebbf5fd --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/MimeParsingOperation.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 . + */ + +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 { + + public ArrayList 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 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 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/MimeParsingResult.java new file mode 100644 index 000000000..05f5125cb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/MimeParsingResult.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.operations.results; + +import android.net.Uri; +import android.os.Parcel; + +import java.util.ArrayList; + +public class MimeParsingResult extends OperationResult { + + public final ArrayList mTemporaryUris; + + public ArrayList getTemporaryUris() { + return mTemporaryUris; + } + + public MimeParsingResult(int result, OperationLog log, ArrayList temporaryUris) { + super(result, log); + mTemporaryUris = temporaryUris; + } + + protected MimeParsingResult(Parcel in) { + super(in); + mTemporaryUris = in.createTypedArrayList(Uri.CREATOR); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeTypedList(mTemporaryUris); + } + + public static final Creator CREATOR = new Creator() { + @Override + public MimeParsingResult createFromParcel(Parcel in) { + return new MimeParsingResult(in); + } + + @Override + public MimeParsingResult[] newArray(int size) { + return new MimeParsingResult[size]; + } + }; +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index d498bd9a1..3c15a2e7b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -787,6 +787,11 @@ public abstract class OperationResult implements Parcelable { MSG_EXPORT_LOG_EXPORT_ERROR_FOPEN(LogLevel.ERROR,R.string.msg_export_log_error_fopen), MSG_EXPORT_LOG_EXPORT_ERROR_WRITING(LogLevel.ERROR,R.string.msg_export_log_error_writing), MSG_EXPORT_LOG_EXPORT_SUCCESS (LogLevel.OK, R.string.msg_export_log_success), + + // mim parsing + MSG_MIME_PARSING(LogLevel.START,R.string.msg_mime_parsing_start), + MSG_MIME_PARSING_ERROR(LogLevel.ERROR,R.string.msg_mime_parsing_error), + MSG_MIME_PARSING_SUCCESS(LogLevel.OK,R.string.msg_mime_parsing_success), ; public final int mMsgId; 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 dca2a08c2..ce4381140 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainService.java @@ -36,6 +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.PromoteKeyOperation; import org.sufficientlysecure.keychain.operations.RevokeOperation; import org.sufficientlysecure.keychain.operations.SignEncryptOperation; @@ -137,6 +138,9 @@ public class KeychainService extends Service implements Progressable { } 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); } else { throw new AssertionError("Unrecognized input parcel in KeychainService!"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/MimeParsingParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/MimeParsingParcel.java new file mode 100644 index 000000000..ccc817c21 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/MimeParsingParcel.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015 Dominik Schürmann + * + * 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 . + */ + +package org.sufficientlysecure.keychain.service; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +public class MimeParsingParcel implements Parcelable { + + private Uri mInputUri; + private Uri mOutputUri; + + public MimeParsingParcel() { + } + + public MimeParsingParcel(Uri inputUri, Uri outputUri) { + mInputUri = inputUri; + mOutputUri = outputUri; + } + + MimeParsingParcel(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()); + } + + public Uri getInputUri() { + return mInputUri; + } + + public Uri getOutputUri() { + return mOutputUri; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mInputUri, 0); + dest.writeParcelable(mOutputUri, 0); + } + + public static final Creator CREATOR = new Creator() { + public MimeParsingParcel createFromParcel(final Parcel source) { + return new MimeParsingParcel(source); + } + + public MimeParsingParcel[] newArray(final int size) { + return new MimeParsingParcel[size]; + } + }; + +} + 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 f57d2d056..640755ef3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -28,7 +28,6 @@ import android.app.Activity; import android.content.ClipDescription; import android.content.Context; import android.content.Intent; -import android.content.pm.LabeledIntent; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.graphics.Point; @@ -38,7 +37,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.os.Parcelable; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -58,14 +56,16 @@ import android.widget.ViewAnimator; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; -import org.sufficientlysecure.keychain.BuildConfig; 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; @@ -432,45 +432,47 @@ public class DecryptListFragment // OpenKeychain's internal viewer if ("text/plain".equals(metadata.getMimeType())) { + parseMime(outputUri); + // this is a significant i/o operation, use an asynctask - new AsyncTask() { - - @Override - protected Intent doInBackground(Void... params) { - - Activity activity = getActivity(); - if (activity == null) { - return null; - } - - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(outputUri, "text/plain"); - intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - return intent; - } - - @Override - protected void onPostExecute(Intent intent) { - // for result so we can possibly get a snackbar error from internal viewer - Activity activity = getActivity(); - if (intent == null || activity == null) { - return; - } - - LabeledIntent internalIntent = new LabeledIntent( - new Intent(intent) - .setClass(activity, DisplayTextActivity.class) - .putExtra(DisplayTextActivity.EXTRA_METADATA, result), - BuildConfig.APPLICATION_ID, R.string.view_internal, R.drawable.ic_launcher); - - Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); - chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, - new Parcelable[] { internalIntent }); - - activity.startActivity(chooserIntent); - } - - }.execute(); +// new AsyncTask() { +// +// @Override +// protected Intent doInBackground(Void... params) { +// +// Activity activity = getActivity(); +// if (activity == null) { +// return null; +// } +// +// Intent intent = new Intent(Intent.ACTION_VIEW); +// intent.setDataAndType(outputUri, "text/plain"); +// intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); +// return intent; +// } +// +// @Override +// protected void onPostExecute(Intent intent) { +// // for result so we can possibly get a snackbar error from internal viewer +// Activity activity = getActivity(); +// if (intent == null || activity == null) { +// return; +// } +// +// LabeledIntent internalIntent = new LabeledIntent( +// new Intent(intent) +// .setClass(activity, DisplayTextActivity.class) +// .putExtra(DisplayTextActivity.EXTRA_METADATA, result), +// BuildConfig.APPLICATION_ID, R.string.view_internal, R.drawable.ic_launcher); +// +// Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); +// chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, +// new Parcelable[] { internalIntent }); +// +// activity.startActivity(chooserIntent); +// } +// +// }.execute(); } else { Intent intent = new Intent(Intent.ACTION_VIEW); @@ -484,6 +486,52 @@ public class DecryptListFragment } + private void parseMime(final Uri inputUri) { + + CryptoOperationHelper.Callback callback + = new CryptoOperationHelper.Callback() { + + @Override + public MimeParsingParcel createOperationInput() { + return new MimeParsingParcel(inputUri, null); + } + + @Override + public void onCryptoOperationSuccess(MimeParsingResult result) { + handleResult(result); + } + + @Override + public void onCryptoOperationCancelled() { + + } + + @Override + public void onCryptoOperationError(MimeParsingResult result) { + handleResult(result); + } + + public void handleResult(MimeParsingResult result) { + // TODO: merge with other log +// saveKeyResult.getLog().add(result, 0); + + mOutputUris = new HashMap<>(result.getTemporaryUris().size()); + for (Uri tempUri : result.getTemporaryUris()) { + // TODO: use same inputUri for all? + mOutputUris.put(inputUri, tempUri); + } + } + + @Override + public boolean onCryptoSetProgress(String msg, int progress, int max) { + return false; + } + }; + + CryptoOperationHelper mimeParsingHelper = new CryptoOperationHelper<>(3, this, callback, R.string.progress_uploading); + mimeParsingHelper.cryptoOperation(); + } + @Override public PgpDecryptVerifyInputParcel createOperationInput() { diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 72406aaab..6aa968756 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1341,6 +1341,11 @@ "I/O error writing to file!" "Log exported successfully!" + + "Parsing the MIME structure" + "MIME parsing failed" + "MIME parsing successfully!" + "Touch to clear passwords." From 3cd54581c33b20a9bfa55f767b245fc6e56e83ef Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 15 Sep 2015 03:02:05 +0200 Subject: [PATCH 02/34] mime: create more general InputDataOperation, which for now and does basic mime parsing --- OpenKeychain/build.gradle | 1 + .../ui/SymmetricTextOperationTests.java | 2 +- .../keychain/ui/ViewKeyAdvShareTest.java | 4 +- .../operations/InputDataOperation.java | 173 +++++++++ .../operations/MimeParsingOperation.java | 360 ------------------ ...arsingResult.java => InputDataResult.java} | 34 +- .../results/InputPendingResult.java | 9 + .../pgp/PgpDecryptVerifyInputParcel.java | 10 + .../provider/TemporaryStorageProvider.java | 22 +- ...arsingParcel.java => InputDataParcel.java} | 39 +- .../keychain/service/KeychainService.java | 31 +- .../keychain/ui/DecryptListFragment.java | 3 - .../keychain/pgp/InputDataOperationTest.java | 166 ++++++++ 13 files changed, 431 insertions(+), 423 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/MimeParsingOperation.java rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/{MimeParsingResult.java => InputDataResult.java} (56%) rename OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/{MimeParsingParcel.java => InputDataParcel.java} (57%) create mode 100644 OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java 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 + * + * 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 . + */ + +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 { + + 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 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 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 - * - * 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 . - */ - -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 { - - public ArrayList 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 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 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 similarity index 56% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/MimeParsingResult.java rename to 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 mTemporaryUris; + public final ArrayList mOutputUris; + public DecryptVerifyResult mDecryptVerifyResult; - public ArrayList getTemporaryUris() { - return mTemporaryUris; + public InputDataResult(OperationLog log, InputPendingResult result) { + super(log, result); + mOutputUris = null; } - public MimeParsingResult(int result, OperationLog log, ArrayList temporaryUris) { + public InputDataResult(int result, OperationLog log, ArrayList 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 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 CREATOR = new Creator() { + public static final Creator CREATOR = new Creator() { @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 similarity index 57% rename from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/MimeParsingParcel.java rename to 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 CREATOR = new Creator() { - public MimeParsingParcel createFromParcel(final Parcel source) { - return new MimeParsingParcel(source); + public static final Creator CREATOR = new Creator() { + 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 + * + * 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 . + */ + +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 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())); + + + } + +} From 326834fd581861e0f8056eb0a5de8205088e7f09 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 15 Sep 2015 23:06:15 +0200 Subject: [PATCH 03/34] mime: add logging to InputDataOperation --- .../operations/InputDataOperation.java | 116 ++++++++++-------- .../operations/results/OperationResult.java | 31 ++++- OpenKeychain/src/main/res/values/strings.xml | 12 ++ .../keychain/pgp/InputDataOperationTest.java | 3 - 4 files changed, 107 insertions(+), 55 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index 030c0a285..cb26080e8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -30,7 +30,6 @@ 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; @@ -38,7 +37,6 @@ 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; @@ -50,7 +48,6 @@ 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. */ @@ -69,13 +66,15 @@ public class InputDataOperation extends BaseOperation { final OperationLog log = new OperationLog(); - log.add(LogType.MSG_MIME_PARSING, 0); + log.add(LogType.MSG_DATA, 0); Uri currentUri; PgpDecryptVerifyInputParcel decryptInput = input.getDecryptInput(); if (decryptInput != null) { + log.add(LogType.MSG_DATA_DECRYPT, 1); + PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable); @@ -88,6 +87,7 @@ public class InputDataOperation extends BaseOperation { if (result.isPending()) { return new InputDataResult(log, result); } + log.addByMerge(result, 2); } else { currentUri = input.getInputUri(); @@ -96,76 +96,96 @@ public class InputDataOperation extends BaseOperation { // If we aren't supposed to attempt mime decode, we are done here if (!input.getMimeDecode()) { + if (decryptInput == null) { + throw new AssertionError("no decryption or mime decoding, this is probably a bug"); + } + + log.add(LogType.MSG_DATA_SKIP_MIME, 1); + ArrayList uris = new ArrayList<>(); uris.add(currentUri); + + log.add(LogType.MSG_DATA_OK, 1); return new InputDataResult(InputDataResult.RESULT_OK, log, uris); } + log.add(LogType.MSG_DATA_MIME, 1); + + InputStream in; try { - InputStream in = mContext.getContentResolver().openInputStream(currentUri); + in = mContext.getContentResolver().openInputStream(currentUri); + } catch (FileNotFoundException e) { + log.add(LogType.MSG_DATA_ERROR_IO, 2); + return new InputDataResult(InputDataResult.RESULT_ERROR, log, null); + } + MimeStreamParser parser = new MimeStreamParser((MimeConfig) null); - MimeStreamParser parser = new MimeStreamParser((MimeConfig) null); + final ArrayList outputUris = new ArrayList<>(); - final ArrayList outputUris = new ArrayList<>(); + parser.setContentDecoding(true); + parser.setRecurse(); + parser.setContentHandler(new AbstractContentHandler() { + String mFilename; - parser.setContentDecoding(true); - parser.setRecurse(); - parser.setContentHandler(new AbstractContentHandler() { - String mFilename; + @Override + public void startHeader() throws MimeException { + mFilename = null; + } - @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_DATA_MIME_PART, 2); + + log.add(LogType.MSG_DATA_MIME_TYPE, 3, bd.getMimeType()); + if (mFilename != null) { + log.add(LogType.MSG_DATA_MIME_FILENAME, 3, mFilename); + } + log.add(LogType.MSG_DATA_MIME_LENGTH, 3, bd.getContentLength()); + + Uri uri = TemporaryStorageProvider.createFile(mContext, mFilename, bd.getMimeType()); + OutputStream out = mContext.getContentResolver().openOutputStream(uri, "w"); + + if (out == null) { + throw new IOException("Error getting file for writing!"); } - @Override - public void field(Field field) throws MimeException { - field = DefaultFieldParser.getParser().parse(field, DecodeMonitor.SILENT); - if (field instanceof ContentDispositionField) { - mFilename = ((ContentDispositionField) field).getFilename(); - } + int len; + while ((len = is.read(buf)) > 0) { + out.write(buf, 0, len); } - @Override - public void body(BodyDescriptor bd, InputStream is) throws MimeException, IOException { + out.close(); + outputUris.add(uri); - // 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); - - } - }); + try { parser.parse(in); + log.add(LogType.MSG_DATA_MIME_OK, 2); - log.add(LogType.MSG_MIME_PARSING_SUCCESS, 1); - + log.add(LogType.MSG_DATA_OK, 1); return new InputDataResult(InputDataResult.RESULT_OK, log, outputUris); - } catch (FileNotFoundException e) { + } catch (IOException e) { e.printStackTrace(); + log.add(LogType.MSG_DATA_MIME_ERROR, 2); 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); + log.add(LogType.MSG_DATA_MIME_ERROR, 2); + return new InputDataResult(InputDataResult.RESULT_ERROR, log, outputUris); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 3856209b3..69e7d3d2d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -126,6 +126,13 @@ public abstract class OperationResult implements Parcelable { Log.v(Constants.TAG, "log: " + this); } + /** Clones this LogEntryParcel, adding extra indent. Note that the parameter array is NOT cloned! */ + public LogEntryParcel (LogEntryParcel original, int extraIndent) { + mType = original.mType; + mParameters = original.mParameters; + mIndent = original.mIndent +extraIndent; + } + public LogEntryParcel(Parcel source) { mType = LogType.values()[source.readInt()]; mParameters = (Object[]) source.readSerializable(); @@ -818,10 +825,19 @@ public abstract class OperationResult implements Parcelable { MSG_KEYBASE_ERROR_PAYLOAD_MISMATCH(LogLevel.ERROR, R.string.msg_keybase_error_msg_payload_mismatch), - // mime parsing - MSG_MIME_PARSING(LogLevel.START,R.string.msg_mime_parsing_start), - MSG_MIME_PARSING_ERROR(LogLevel.ERROR,R.string.msg_mime_parsing_error), - MSG_MIME_PARSING_SUCCESS(LogLevel.OK,R.string.msg_mime_parsing_success), + // InputData Operation + MSG_DATA (LogLevel.START, R.string.msg_data), + MSG_DATA_DECRYPT (LogLevel.DEBUG, R.string.msg_data_decrypt), + MSG_DATA_ERROR_IO (LogLevel.ERROR, R.string.msg_data_error_io), + MSG_DATA_MIME_ERROR (LogLevel.ERROR, R.string.msg_data_mime_error), + MSG_DATA_MIME_FILENAME (LogLevel.DEBUG, R.string.msg_data_mime_filename), + MSG_DATA_MIME_LENGTH (LogLevel.DEBUG, R.string.msg_data_mime_length), + MSG_DATA_MIME (LogLevel.DEBUG, R.string.msg_data_mime), + MSG_DATA_MIME_OK (LogLevel.INFO, R.string.msg_data_mime_ok), + MSG_DATA_MIME_PART (LogLevel.DEBUG, R.string.msg_data_mime_part), + MSG_DATA_MIME_TYPE (LogLevel.DEBUG, R.string.msg_data_mime_type), + MSG_DATA_OK (LogLevel.OK, R.string.msg_data_ok), + MSG_DATA_SKIP_MIME (LogLevel.DEBUG, R.string.msg_data_skip_mime), MSG_LV (LogLevel.START, R.string.msg_lv), MSG_LV_MATCH (LogLevel.DEBUG, R.string.msg_lv_match), @@ -901,6 +917,13 @@ public abstract class OperationResult implements Parcelable { mParcels.add(new SubLogEntryParcel(subResult, subLog.getFirst().mType, indent, subLog.getFirst().mParameters)); } + public void addByMerge(OperationResult subResult, int indent) { + OperationLog subLog = subResult.getLog(); + for (LogEntryParcel entry : subLog) { + mParcels.add(new LogEntryParcel(entry, indent)); + } + } + public SubLogEntryParcel getSubResultIfSingle() { if (mParcels.size() != 1) { return null; diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 56bb95f3f..a1e21e661 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1355,6 +1355,18 @@ "Format error!" "Resource not found!" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" "Account saved" diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java index 71673bdb7..59b52ea85 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java @@ -37,8 +37,6 @@ 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; @@ -50,7 +48,6 @@ 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; From 99fd1f4c22c51fe4967517c5cf69dc3af9fe5378 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 00:05:21 +0200 Subject: [PATCH 04/34] preliminary working mime parsing in DecryptListFragment! (beware WIP, here be dragons!) --- .../operations/InputDataOperation.java | 20 ++--- .../operations/results/InputDataResult.java | 14 +++- .../keychain/service/InputDataParcel.java | 1 + .../keychain/ui/DecryptListFragment.java | 81 ++++++++++--------- 4 files changed, 66 insertions(+), 50 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index cb26080e8..7e93ad0d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -70,6 +70,8 @@ public class InputDataOperation extends BaseOperation { Uri currentUri; + DecryptVerifyResult decryptResult = null; + PgpDecryptVerifyInputParcel decryptInput = input.getDecryptInput(); if (decryptInput != null) { @@ -83,11 +85,11 @@ public class InputDataOperation extends BaseOperation { currentUri = TemporaryStorageProvider.createFile(mContext); decryptInput.setOutputUri(currentUri); - DecryptVerifyResult result = op.execute(decryptInput, cryptoInput); - if (result.isPending()) { - return new InputDataResult(log, result); + decryptResult = op.execute(decryptInput, cryptoInput); + if (decryptResult.isPending()) { + return new InputDataResult(log, decryptResult); } - log.addByMerge(result, 2); + log.addByMerge(decryptResult, 2); } else { currentUri = input.getInputUri(); @@ -106,7 +108,7 @@ public class InputDataOperation extends BaseOperation { uris.add(currentUri); log.add(LogType.MSG_DATA_OK, 1); - return new InputDataResult(InputDataResult.RESULT_OK, log, uris); + return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, uris); } @@ -117,7 +119,7 @@ public class InputDataOperation extends BaseOperation { in = mContext.getContentResolver().openInputStream(currentUri); } catch (FileNotFoundException e) { log.add(LogType.MSG_DATA_ERROR_IO, 2); - return new InputDataResult(InputDataResult.RESULT_ERROR, log, null); + return new InputDataResult(InputDataResult.RESULT_ERROR, log); } MimeStreamParser parser = new MimeStreamParser((MimeConfig) null); @@ -176,16 +178,16 @@ public class InputDataOperation extends BaseOperation { log.add(LogType.MSG_DATA_MIME_OK, 2); log.add(LogType.MSG_DATA_OK, 1); - return new InputDataResult(InputDataResult.RESULT_OK, log, outputUris); + return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, outputUris); } catch (IOException e) { e.printStackTrace(); log.add(LogType.MSG_DATA_MIME_ERROR, 2); - return new InputDataResult(InputDataResult.RESULT_ERROR, log, null); + return new InputDataResult(InputDataResult.RESULT_ERROR, log); } catch (MimeException e) { e.printStackTrace(); log.add(LogType.MSG_DATA_MIME_ERROR, 2); - return new InputDataResult(InputDataResult.RESULT_ERROR, log, outputUris); + return new InputDataResult(InputDataResult.RESULT_ERROR, log); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java index 908636ca7..e3432e637 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java @@ -25,21 +25,30 @@ import java.util.ArrayList; public class InputDataResult extends InputPendingResult { public final ArrayList mOutputUris; - public DecryptVerifyResult mDecryptVerifyResult; + final public DecryptVerifyResult mDecryptVerifyResult; public InputDataResult(OperationLog log, InputPendingResult result) { super(log, result); mOutputUris = null; + mDecryptVerifyResult = null; } - public InputDataResult(int result, OperationLog log, ArrayList temporaryUris) { + public InputDataResult(int result, OperationLog log, DecryptVerifyResult decryptResult, ArrayList temporaryUris) { super(result, log); mOutputUris = temporaryUris; + mDecryptVerifyResult = decryptResult; + } + + public InputDataResult(int result, OperationLog log) { + super(result, log); + mOutputUris = null; + mDecryptVerifyResult = null; } protected InputDataResult(Parcel in) { super(in); mOutputUris = in.createTypedArrayList(Uri.CREATOR); + mDecryptVerifyResult = in.readParcelable(DecryptVerifyResult.class.getClassLoader()); } public ArrayList getOutputUris() { @@ -55,6 +64,7 @@ public class InputDataResult extends InputPendingResult { public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeTypedList(mOutputUris); + dest.writeParcelable(mDecryptVerifyResult, 0); } public static final Creator CREATOR = new Creator() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/InputDataParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/InputDataParcel.java index 807577001..6affd3334 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/InputDataParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/InputDataParcel.java @@ -33,6 +33,7 @@ public class InputDataParcel implements Parcelable { public InputDataParcel(Uri inputUri, PgpDecryptVerifyInputParcel decryptInput) { mInputUri = inputUri; + mDecryptInput = decryptInput; } InputDataParcel(Parcel source) { 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 ddaf40010..df896322d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -58,10 +58,12 @@ 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.InputDataResult; 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.InputDataParcel; import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel; @@ -76,7 +78,7 @@ import org.sufficientlysecure.keychain.util.ParcelableHashMap; public class DecryptListFragment - extends QueueingCryptoOperationFragment + extends QueueingCryptoOperationFragment implements OnMenuItemClickListener { public static final String ARG_INPUT_URIS = "input_uris"; @@ -88,7 +90,7 @@ public class DecryptListFragment public static final String ARG_CURRENT_URI = "current_uri"; private ArrayList mInputUris; - private HashMap mOutputUris; + private HashMap mInputDataResults; private ArrayList mPendingInputUris; private ArrayList mCancelledInputUris; @@ -141,19 +143,19 @@ public class DecryptListFragment outState.putParcelableArrayList(ARG_INPUT_URIS, mInputUris); - HashMap results = new HashMap<>(mInputUris.size()); + HashMap results = new HashMap<>(mInputUris.size()); for (Uri uri : mInputUris) { if (mPendingInputUris.contains(uri)) { continue; } - DecryptVerifyResult result = mAdapter.getItemResult(uri); + InputDataResult result = mAdapter.getItemResult(uri); if (result != null) { results.put(uri, result); } } outState.putParcelable(ARG_RESULTS, new ParcelableHashMap<>(results)); - outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mOutputUris)); + outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mInputDataResults)); outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris); outState.putParcelable(ARG_CURRENT_URI, mCurrentInputUri); @@ -167,23 +169,20 @@ public class DecryptListFragment ArrayList inputUris = getArguments().getParcelableArrayList(ARG_INPUT_URIS); ArrayList cancelledUris = args.getParcelableArrayList(ARG_CANCELLED_URIS); - ParcelableHashMap outputUris = args.getParcelable(ARG_OUTPUT_URIS); - ParcelableHashMap results = args.getParcelable(ARG_RESULTS); + ParcelableHashMap results = args.getParcelable(ARG_RESULTS); Uri currentInputUri = args.getParcelable(ARG_CURRENT_URI); displayInputUris(inputUris, currentInputUri, cancelledUris, - outputUris != null ? outputUris.getMap() : null, results != null ? results.getMap() : null ); } private void displayInputUris(ArrayList inputUris, Uri currentInputUri, - ArrayList cancelledUris, HashMap outputUris, - HashMap results) { + ArrayList cancelledUris, HashMap results) { mInputUris = inputUris; mCurrentInputUri = currentInputUri; - mOutputUris = outputUris != null ? outputUris : new HashMap(inputUris.size()); + mInputDataResults = results != null ? results : new HashMap(inputUris.size()); mCancelledInputUris = cancelledUris != null ? cancelledUris : new ArrayList(); mPendingInputUris = new ArrayList<>(); @@ -206,9 +205,8 @@ public class DecryptListFragment } if (results != null && results.containsKey(uri)) { - processResult(uri, results.get(uri)); + processResult(uri); } else { - mOutputUris.put(uri, TemporaryStorageProvider.createFile(getActivity())); mPendingInputUris.add(uri); } } @@ -224,7 +222,7 @@ public class DecryptListFragment case REQUEST_CODE_OUTPUT: { // This happens after output file was selected, so start our operation if (resultCode == Activity.RESULT_OK && data != null) { - Uri decryptedFileUri = mOutputUris.get(mCurrentInputUri); + Uri decryptedFileUri = mInputDataResults.get(mCurrentInputUri).getOutputUris().get(0); Uri saveUri = data.getData(); saveFile(decryptedFileUri, saveUri); mCurrentInputUri = null; @@ -260,7 +258,7 @@ public class DecryptListFragment } @Override - public void onQueuedOperationError(DecryptVerifyResult result) { + public void onQueuedOperationError(InputDataResult result) { final Uri uri = mCurrentInputUri; mCurrentInputUri = null; @@ -270,11 +268,12 @@ public class DecryptListFragment } @Override - public void onQueuedOperationSuccess(DecryptVerifyResult result) { + public void onQueuedOperationSuccess(InputDataResult result) { Uri uri = mCurrentInputUri; mCurrentInputUri = null; - processResult(uri, result); + mInputDataResults.put(uri, result); + processResult(uri); cryptoOperation(); } @@ -298,19 +297,21 @@ public class DecryptListFragment } - private void processResult(final Uri uri, final DecryptVerifyResult result) { + private void processResult(final Uri uri) { new AsyncTask() { @Override protected Drawable doInBackground(Void... params) { + InputDataResult result = mInputDataResults.get(uri); + Context context = getActivity(); - if (result.getDecryptionMetadata() == null || context == null) { + if (result.mDecryptVerifyResult.getDecryptionMetadata() == null || context == null) { return null; } - String type = result.getDecryptionMetadata().getMimeType(); - Uri outputUri = mOutputUris.get(uri); + String type = result.mDecryptVerifyResult.getDecryptionMetadata().getMimeType(); + Uri outputUri = result.getOutputUris().get(0); if (type == null || outputUri == null) { return null; } @@ -339,17 +340,19 @@ public class DecryptListFragment @Override protected void onPostExecute(Drawable icon) { - processResult(uri, result, icon); + processResult(uri, icon); } }.execute(); } - private void processResult(final Uri uri, DecryptVerifyResult result, Drawable icon) { + private void processResult(final Uri uri, Drawable icon) { + + InputDataResult result = mInputDataResults.get(uri); OnClickListener onFileClick = null, onKeyClick = null; - OpenPgpSignatureResult sigResult = result.getSignatureResult(); + OpenPgpSignatureResult sigResult = result.mDecryptVerifyResult.getSignatureResult(); if (sigResult != null) { final long keyId = sigResult.getKeyId(); if (sigResult.getResult() != OpenPgpSignatureResult.RESULT_KEY_MISSING) { @@ -368,7 +371,7 @@ public class DecryptListFragment } } - if (result.success() && result.getDecryptionMetadata() != null) { + if (result.success() && result.mDecryptVerifyResult.getDecryptionMetadata() != null) { onFileClick = new OnClickListener() { @Override public void onClick(View view) { @@ -403,8 +406,8 @@ public class DecryptListFragment return; } - final Uri outputUri = mOutputUris.get(uri); - final DecryptVerifyResult result = mAdapter.getItemResult(uri); + final Uri outputUri = mInputDataResults.get(uri).getOutputUris().get(0); + final DecryptVerifyResult result = mAdapter.getItemResult(uri).mDecryptVerifyResult; if (outputUri == null || result == null) { return; } @@ -460,7 +463,7 @@ public class DecryptListFragment } @Override - public PgpDecryptVerifyInputParcel createOperationInput() { + public InputDataParcel createOperationInput() { if (mCurrentInputUri == null) { if (mPendingInputUris.isEmpty()) { @@ -471,11 +474,11 @@ public class DecryptListFragment mCurrentInputUri = mPendingInputUris.remove(0); } - Uri currentOutputUri = mOutputUris.get(mCurrentInputUri); - Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri + ", mOutputUri=" + currentOutputUri); + Log.d(Constants.TAG, "mInputUri=" + mCurrentInputUri); - return new PgpDecryptVerifyInputParcel(mCurrentInputUri, currentOutputUri) + PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel() .setAllowSymmetricDecryption(true); + return new InputDataParcel(mCurrentInputUri, decryptInput); } @@ -496,7 +499,7 @@ public class DecryptListFragment } ViewModel model = mAdapter.mMenuClickedModel; - DecryptVerifyResult result = model.mResult; + DecryptVerifyResult result = model.mResult.mDecryptVerifyResult; switch (menuItem.getItemId()) { case R.id.view_log: Intent intent = new Intent(activity, LogDisplayActivity.class); @@ -553,7 +556,7 @@ public class DecryptListFragment } - public static class DecryptFilesAdapter extends RecyclerView.Adapter { + public class DecryptFilesAdapter extends RecyclerView.Adapter { private Context mContext; private ArrayList mDataset; private OnMenuItemClickListener mMenuItemClickListener; @@ -562,7 +565,7 @@ public class DecryptListFragment public class ViewModel { Context mContext; Uri mInputUri; - DecryptVerifyResult mResult; + InputDataResult mResult; Drawable mIcon; OnClickListener mOnFileClickListener; @@ -580,7 +583,7 @@ public class DecryptListFragment mCancelled = null; } - void addResult(DecryptVerifyResult result) { + void addResult(InputDataResult result) { mResult = result; } @@ -701,9 +704,9 @@ public class DecryptListFragment holder.vAnimator.setDisplayedChild(1); } - KeyFormattingUtils.setStatus(mContext, holder, model.mResult); + KeyFormattingUtils.setStatus(mContext, holder, model.mResult.mDecryptVerifyResult); - final OpenPgpMetadata metadata = model.mResult.getDecryptionMetadata(); + final OpenPgpMetadata metadata = model.mResult.mDecryptVerifyResult.getDecryptionMetadata(); String filename; if (metadata == null) { @@ -775,7 +778,7 @@ public class DecryptListFragment return mDataset.size(); } - public DecryptVerifyResult getItemResult(Uri uri) { + public InputDataResult getItemResult(Uri uri) { ViewModel model = new ViewModel(mContext, uri); int pos = mDataset.indexOf(model); if (pos == -1) { @@ -806,7 +809,7 @@ public class DecryptListFragment notifyItemChanged(pos); } - public void addResult(Uri uri, DecryptVerifyResult result, Drawable icon, + public void addResult(Uri uri, InputDataResult result, Drawable icon, OnClickListener onFileClick, OnClickListener onKeyClick) { ViewModel model = new ViewModel(mContext, uri); From 13509a0b90eb7646e646646dc2f61db8da411cf7 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 01:14:40 +0200 Subject: [PATCH 05/34] multidecrypt: inline setOnClickListener --- .../keychain/ui/DecryptListFragment.java | 72 +++++++------------ 1 file changed, 26 insertions(+), 46 deletions(-) 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 df896322d..99f4c8979 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -262,7 +262,7 @@ public class DecryptListFragment final Uri uri = mCurrentInputUri; mCurrentInputUri = null; - mAdapter.addResult(uri, result, null, null, null); + mAdapter.addResult(uri, result, null); cryptoOperation(); } @@ -349,38 +349,7 @@ public class DecryptListFragment private void processResult(final Uri uri, Drawable icon) { InputDataResult result = mInputDataResults.get(uri); - - OnClickListener onFileClick = null, onKeyClick = null; - - OpenPgpSignatureResult sigResult = result.mDecryptVerifyResult.getSignatureResult(); - if (sigResult != null) { - final long keyId = sigResult.getKeyId(); - if (sigResult.getResult() != OpenPgpSignatureResult.RESULT_KEY_MISSING) { - onKeyClick = new OnClickListener() { - @Override - public void onClick(View view) { - Activity activity = getActivity(); - if (activity == null) { - return; - } - Intent intent = new Intent(activity, ViewKeyActivity.class); - intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); - activity.startActivity(intent); - } - }; - } - } - - if (result.success() && result.mDecryptVerifyResult.getDecryptionMetadata() != null) { - onFileClick = new OnClickListener() { - @Override - public void onClick(View view) { - displayWithViewIntent(uri, false); - } - }; - } - - mAdapter.addResult(uri, result, icon, onFileClick, onKeyClick); + mAdapter.addResult(uri, result, icon); } @@ -568,9 +537,6 @@ public class DecryptListFragment InputDataResult mResult; Drawable mIcon; - OnClickListener mOnFileClickListener; - OnClickListener mOnKeyClickListener; - int mProgress, mMax; String mProgressMsg; OnClickListener mCancelled; @@ -591,11 +557,6 @@ public class DecryptListFragment mIcon = icon; } - void setOnClickListeners(OnClickListener onFileClick, OnClickListener onKeyClick) { - mOnFileClickListener = onFileClick; - mOnKeyClickListener = onKeyClick; - } - boolean hasResult() { return mResult != null; } @@ -732,8 +693,29 @@ public class DecryptListFragment holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); } - holder.vFile.setOnClickListener(model.mOnFileClickListener); - holder.vSignatureLayout.setOnClickListener(model.mOnKeyClickListener); + holder.vFile.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (model.mResult.success() && model.mResult.mDecryptVerifyResult.getDecryptionMetadata() != null) { + displayWithViewIntent(model.mInputUri, false); + } + } + }); + + OpenPgpSignatureResult sigResult = model.mResult.mDecryptVerifyResult.getSignatureResult(); + if (sigResult != null) { + final long keyId = sigResult.getKeyId(); + if (sigResult.getResult() != OpenPgpSignatureResult.RESULT_KEY_MISSING) { + holder.vSignatureLayout.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(mContext, ViewKeyActivity.class); + intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); + mContext.startActivity(intent); + } + }); + } + } holder.vContextMenu.setTag(model); holder.vContextMenu.setOnClickListener(new OnClickListener() { @@ -809,8 +791,7 @@ public class DecryptListFragment notifyItemChanged(pos); } - public void addResult(Uri uri, InputDataResult result, Drawable icon, - OnClickListener onFileClick, OnClickListener onKeyClick) { + public void addResult(Uri uri, InputDataResult result, Drawable icon) { ViewModel model = new ViewModel(mContext, uri); int pos = mDataset.indexOf(model); @@ -820,7 +801,6 @@ public class DecryptListFragment if (icon != null) { model.addIcon(icon); } - model.setOnClickListeners(onFileClick, onKeyClick); notifyItemChanged(pos); } From 96865ccbaaeaefe6af6b90ed664d1390a32417ad Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 01:20:16 +0200 Subject: [PATCH 06/34] multidecrypt: don't pass context to adapter --- .../keychain/ui/DecryptListFragment.java | 48 +++++++++++-------- .../keychain/ui/util/KeyFormattingUtils.java | 11 +++-- 2 files changed, 34 insertions(+), 25 deletions(-) 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 99f4c8979..9cc8a0887 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -131,7 +131,7 @@ public class DecryptListFragment vFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); vFilesList.setItemAnimator(new DefaultItemAnimator()); - mAdapter = new DecryptFilesAdapter(getActivity(), this); + mAdapter = new DecryptFilesAdapter(this); vFilesList.setAdapter(mAdapter); return view; @@ -526,13 +526,11 @@ public class DecryptListFragment } public class DecryptFilesAdapter extends RecyclerView.Adapter { - private Context mContext; private ArrayList mDataset; private OnMenuItemClickListener mMenuItemClickListener; private ViewModel mMenuClickedModel; public class ViewModel { - Context mContext; Uri mInputUri; InputDataResult mResult; Drawable mIcon; @@ -541,8 +539,7 @@ public class DecryptListFragment String mProgressMsg; OnClickListener mCancelled; - ViewModel(Context context, Uri uri) { - mContext = context; + ViewModel(Uri uri) { mInputUri = uri; mProgress = 0; mMax = 100; @@ -600,8 +597,7 @@ public class DecryptListFragment } // Provide a suitable constructor (depends on the kind of dataset) - public DecryptFilesAdapter(Context context, OnMenuItemClickListener menuItemClickListener) { - mContext = context; + public DecryptFilesAdapter(OnMenuItemClickListener menuItemClickListener) { mMenuItemClickListener = menuItemClickListener; mDataset = new ArrayList<>(); } @@ -665,15 +661,15 @@ public class DecryptListFragment holder.vAnimator.setDisplayedChild(1); } - KeyFormattingUtils.setStatus(mContext, holder, model.mResult.mDecryptVerifyResult); + KeyFormattingUtils.setStatus(getResources(), holder, model.mResult.mDecryptVerifyResult); final OpenPgpMetadata metadata = model.mResult.mDecryptVerifyResult.getDecryptionMetadata(); String filename; if (metadata == null) { - filename = mContext.getString(R.string.filename_unknown); + filename = getString(R.string.filename_unknown); } else if (TextUtils.isEmpty(metadata.getFilename())) { - filename = mContext.getString("text/plain".equals(metadata.getMimeType()) + filename = getString("text/plain".equals(metadata.getMimeType()) ? R.string.filename_unknown_text : R.string.filename_unknown); } else { filename = metadata.getFilename(); @@ -709,9 +705,13 @@ public class DecryptListFragment holder.vSignatureLayout.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { - Intent intent = new Intent(mContext, ViewKeyActivity.class); + Activity activity = getActivity(); + if (activity == null) { + return; + } + Intent intent = new Intent(activity, ViewKeyActivity.class); intent.setData(KeyRings.buildUnifiedKeyRingUri(keyId)); - mContext.startActivity(intent); + activity.startActivity(intent); } }); } @@ -721,8 +721,12 @@ public class DecryptListFragment holder.vContextMenu.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { + Activity activity = getActivity(); + if (activity == null) { + return; + } mMenuClickedModel = model; - PopupMenu menu = new PopupMenu(mContext, view); + PopupMenu menu = new PopupMenu(activity, view); menu.inflate(R.menu.decrypt_item_context_menu); menu.setOnMenuItemClickListener(mMenuItemClickListener); menu.setOnDismissListener(new OnDismissListener() { @@ -746,9 +750,13 @@ public class DecryptListFragment holder.vErrorViewLog.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - Intent intent = new Intent(mContext, LogDisplayActivity.class); + Activity activity = getActivity(); + if (activity == null) { + return; + } + Intent intent = new Intent(activity, LogDisplayActivity.class); intent.putExtra(LogDisplayFragment.EXTRA_RESULT, model.mResult); - mContext.startActivity(intent); + activity.startActivity(intent); } }); @@ -761,7 +769,7 @@ public class DecryptListFragment } public InputDataResult getItemResult(Uri uri) { - ViewModel model = new ViewModel(mContext, uri); + ViewModel model = new ViewModel(uri); int pos = mDataset.indexOf(model); if (pos == -1) { return null; @@ -772,20 +780,20 @@ public class DecryptListFragment } public void add(Uri uri) { - ViewModel newModel = new ViewModel(mContext, uri); + ViewModel newModel = new ViewModel(uri); mDataset.add(newModel); notifyItemInserted(mDataset.size()); } public void setProgress(Uri uri, int progress, int max, String msg) { - ViewModel newModel = new ViewModel(mContext, uri); + ViewModel newModel = new ViewModel(uri); int pos = mDataset.indexOf(newModel); mDataset.get(pos).setProgress(progress, max, msg); notifyItemChanged(pos); } public void setCancelled(Uri uri, OnClickListener retryListener) { - ViewModel newModel = new ViewModel(mContext, uri); + ViewModel newModel = new ViewModel(uri); int pos = mDataset.indexOf(newModel); mDataset.get(pos).setCancelled(retryListener); notifyItemChanged(pos); @@ -793,7 +801,7 @@ public class DecryptListFragment public void addResult(Uri uri, InputDataResult result, Drawable icon) { - ViewModel model = new ViewModel(mContext, uri); + ViewModel model = new ViewModel(uri); int pos = mDataset.indexOf(model); model = mDataset.get(pos); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index 284c17e7a..8f5753dae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.util; import android.content.Context; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.PorterDuff; import android.text.Spannable; @@ -446,7 +447,7 @@ public class KeyFormattingUtils { } @SuppressWarnings("deprecation") // context.getDrawable is api lvl 21, need to use deprecated - public static void setStatus(Context context, StatusHolder holder, DecryptVerifyResult result) { + public static void setStatus(Resources resources, StatusHolder holder, DecryptVerifyResult result) { if (holder.hasEncrypt()) { OpenPgpDecryptionResult decryptionResult = result.getDecryptionResult(); @@ -477,9 +478,9 @@ public class KeyFormattingUtils { } } - int encColorRes = context.getResources().getColor(encColor); + int encColorRes = resources.getColor(encColor); holder.getEncryptionStatusIcon().setColorFilter(encColorRes, PorterDuff.Mode.SRC_IN); - holder.getEncryptionStatusIcon().setImageDrawable(context.getResources().getDrawable(encIcon)); + holder.getEncryptionStatusIcon().setImageDrawable(resources.getDrawable(encIcon)); holder.getEncryptionStatusText().setText(encText); holder.getEncryptionStatusText().setTextColor(encColorRes); } @@ -577,9 +578,9 @@ public class KeyFormattingUtils { } - int sigColorRes = context.getResources().getColor(sigColor); + int sigColorRes = resources.getColor(sigColor); holder.getSignatureStatusIcon().setColorFilter(sigColorRes, PorterDuff.Mode.SRC_IN); - holder.getSignatureStatusIcon().setImageDrawable(context.getResources().getDrawable(sigIcon)); + holder.getSignatureStatusIcon().setImageDrawable(resources.getDrawable(sigIcon)); holder.getSignatureStatusText().setText(sigText); holder.getSignatureStatusText().setTextColor(sigColorRes); From 0ed0ba88f9ce4647f77a823fdf218ce53b175c48 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 01:34:21 +0200 Subject: [PATCH 07/34] mime: return one OpenPgpMetadata object per body part --- .../operations/InputDataOperation.java | 11 +++++-- .../operations/results/InputDataResult.java | 33 ++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index 7e93ad0d0..14f711df0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -37,6 +37,7 @@ 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.openintents.openpgp.OpenPgpMetadata; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.InputDataResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; @@ -106,9 +107,11 @@ public class InputDataOperation extends BaseOperation { ArrayList uris = new ArrayList<>(); uris.add(currentUri); + ArrayList metadatas = new ArrayList<>(); + metadatas.add(decryptResult.getDecryptionMetadata()); log.add(LogType.MSG_DATA_OK, 1); - return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, uris); + return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, uris, metadatas); } @@ -124,6 +127,7 @@ public class InputDataOperation extends BaseOperation { MimeStreamParser parser = new MimeStreamParser((MimeConfig) null); final ArrayList outputUris = new ArrayList<>(); + final ArrayList metadatas = new ArrayList<>(); parser.setContentDecoding(true); parser.setRecurse(); @@ -166,8 +170,11 @@ public class InputDataOperation extends BaseOperation { out.write(buf, 0, len); } + OpenPgpMetadata metadata = new OpenPgpMetadata(mFilename, bd.getMimeType(), 0L, bd.getContentLength()); + out.close(); outputUris.add(uri); + metadatas.add(metadata); } }); @@ -178,7 +185,7 @@ public class InputDataOperation extends BaseOperation { log.add(LogType.MSG_DATA_MIME_OK, 2); log.add(LogType.MSG_DATA_OK, 1); - return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, outputUris); + return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, outputUris, metadatas); } catch (IOException e) { e.printStackTrace(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java index e3432e637..0e4bddb4c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java @@ -17,38 +17,52 @@ package org.sufficientlysecure.keychain.operations.results; -import android.net.Uri; -import android.os.Parcel; import java.util.ArrayList; +import android.net.Uri; +import android.os.Parcel; +import android.support.annotation.NonNull; + +import org.openintents.openpgp.OpenPgpMetadata; + + public class InputDataResult extends InputPendingResult { public final ArrayList mOutputUris; final public DecryptVerifyResult mDecryptVerifyResult; + public final ArrayList mMetadata; - public InputDataResult(OperationLog log, InputPendingResult result) { + public InputDataResult(OperationLog log, @NonNull InputPendingResult result) { super(log, result); mOutputUris = null; mDecryptVerifyResult = null; - } - - public InputDataResult(int result, OperationLog log, DecryptVerifyResult decryptResult, ArrayList temporaryUris) { - super(result, log); - mOutputUris = temporaryUris; - mDecryptVerifyResult = decryptResult; + mMetadata = null; } public InputDataResult(int result, OperationLog log) { super(result, log); mOutputUris = null; mDecryptVerifyResult = null; + mMetadata = null; + } + + public InputDataResult(int result, OperationLog log, DecryptVerifyResult decryptResult, + @NonNull ArrayList outputUris, @NonNull ArrayList metadata) { + super(result, log); + mDecryptVerifyResult = decryptResult; + if (outputUris.size() == metadata.size()) { + throw new AssertionError("number of output URIs must match metadata!"); + } + mOutputUris = outputUris; + mMetadata = metadata; } protected InputDataResult(Parcel in) { super(in); mOutputUris = in.createTypedArrayList(Uri.CREATOR); mDecryptVerifyResult = in.readParcelable(DecryptVerifyResult.class.getClassLoader()); + mMetadata = in.createTypedArrayList(OpenPgpMetadata.CREATOR); } public ArrayList getOutputUris() { @@ -65,6 +79,7 @@ public class InputDataResult extends InputPendingResult { super.writeToParcel(dest, flags); dest.writeTypedList(mOutputUris); dest.writeParcelable(mDecryptVerifyResult, 0); + dest.writeTypedList(mMetadata); } public static final Creator CREATOR = new Creator() { From 258e2e9a883c4bf317cda55099efce92b7c1bf08 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 01:49:42 +0200 Subject: [PATCH 08/34] multidecrypt: change some method params, prepare for open/share of other outputs --- .../keychain/ui/DecryptListFragment.java | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) 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 9cc8a0887..99a38ed16 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -57,19 +57,16 @@ import org.openintents.openpgp.OpenPgpMetadata; 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.InputDataResult; 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.InputDataParcel; -import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; @@ -306,17 +303,13 @@ public class DecryptListFragment InputDataResult result = mInputDataResults.get(uri); Context context = getActivity(); - if (result.mDecryptVerifyResult.getDecryptionMetadata() == null || context == null) { - return null; - } - - String type = result.mDecryptVerifyResult.getDecryptionMetadata().getMimeType(); + OpenPgpMetadata metadata = result.mMetadata.get(0); Uri outputUri = result.getOutputUris().get(0); - if (type == null || outputUri == null) { + if (metadata == null || context == null || outputUri == null) { return null; } - TemporaryStorageProvider.setMimeType(context, outputUri, type); + String type = metadata.getMimeType(); if (ClipDescription.compareMimeTypes(type, "image/*")) { int px = FormattingUtils.dpToPx(context, 48); @@ -369,19 +362,14 @@ public class DecryptListFragment } - public void displayWithViewIntent(final Uri uri, boolean share) { + public void displayWithViewIntent(InputDataResult result, int index, boolean share) { Activity activity = getActivity(); - if (activity == null || mCurrentInputUri != null) { + if (activity == null) { return; } - final Uri outputUri = mInputDataResults.get(uri).getOutputUris().get(0); - final DecryptVerifyResult result = mAdapter.getItemResult(uri).mDecryptVerifyResult; - if (outputUri == null || result == null) { - return; - } - - final OpenPgpMetadata metadata = result.getDecryptionMetadata(); + Uri outputUri = result.getOutputUris().get(index); + OpenPgpMetadata metadata = result.mMetadata.get(index); // text/plain is a special case where we extract the uri content into // the EXTRA_TEXT extra ourselves, and display a chooser which includes @@ -390,7 +378,7 @@ public class DecryptListFragment if (share) { try { - String plaintext = FileHelper.readTextFromUri(activity, outputUri, result.getCharset()); + String plaintext = FileHelper.readTextFromUri(activity, outputUri, null); Intent intent = new Intent(Intent.ACTION_SEND); intent.setType(metadata.getMimeType()); @@ -468,18 +456,17 @@ public class DecryptListFragment } ViewModel model = mAdapter.mMenuClickedModel; - DecryptVerifyResult result = model.mResult.mDecryptVerifyResult; switch (menuItem.getItemId()) { case R.id.view_log: Intent intent = new Intent(activity, LogDisplayActivity.class); - intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, model.mResult); activity.startActivity(intent); return true; case R.id.decrypt_share: - displayWithViewIntent(model.mInputUri, true); + displayWithViewIntent(model.mResult, 0, true); return true; case R.id.decrypt_save: - OpenPgpMetadata metadata = result.getDecryptionMetadata(); + OpenPgpMetadata metadata = model.mResult.mDecryptVerifyResult.getDecryptionMetadata(); if (metadata == null) { return true; } @@ -663,7 +650,7 @@ public class DecryptListFragment KeyFormattingUtils.setStatus(getResources(), holder, model.mResult.mDecryptVerifyResult); - final OpenPgpMetadata metadata = model.mResult.mDecryptVerifyResult.getDecryptionMetadata(); + final OpenPgpMetadata metadata = model.mResult.mMetadata.get(0); String filename; if (metadata == null) { @@ -692,8 +679,8 @@ public class DecryptListFragment holder.vFile.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { - if (model.mResult.success() && model.mResult.mDecryptVerifyResult.getDecryptionMetadata() != null) { - displayWithViewIntent(model.mInputUri, false); + if (model.mResult.success()) { + displayWithViewIntent(model.mResult, 0, false); } } }); From f90f86ae04cd3ecdc5ca1b9c9c013e8891ffc88e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 01:52:45 +0200 Subject: [PATCH 09/34] fix messed up imports --- .../sufficientlysecure/keychain/ui/DecryptListFragment.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 99a38ed16..857c8f283 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -61,12 +61,13 @@ import org.sufficientlysecure.keychain.operations.results.InputDataResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.InputDataParcel; +import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; +// this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; -import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.FileHelper; From 5ac939387f35e634bec23f79220e6f8e7ff909d9 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 02:11:13 +0200 Subject: [PATCH 10/34] multidecrypt: put drawable cache in map attribute --- .../keychain/ui/DecryptListFragment.java | 85 ++++++++++--------- 1 file changed, 44 insertions(+), 41 deletions(-) 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 857c8f283..27ad9cbb1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -260,7 +260,7 @@ public class DecryptListFragment final Uri uri = mCurrentInputUri; mCurrentInputUri = null; - mAdapter.addResult(uri, result, null); + mAdapter.addResult(uri, result); cryptoOperation(); } @@ -295,37 +295,54 @@ public class DecryptListFragment } + HashMap mIconCache = new HashMap<>(); + private void processResult(final Uri uri) { - new AsyncTask() { + new AsyncTask() { @Override - protected Drawable doInBackground(Void... params) { + protected Void doInBackground(Void... params) { InputDataResult result = mInputDataResults.get(uri); Context context = getActivity(); - OpenPgpMetadata metadata = result.mMetadata.get(0); - Uri outputUri = result.getOutputUris().get(0); - if (metadata == null || context == null || outputUri == null) { + if (context == null) { return null; } - String type = metadata.getMimeType(); + for (int i = 0; i < result.getOutputUris().size(); i++) { - if (ClipDescription.compareMimeTypes(type, "image/*")) { - int px = FormattingUtils.dpToPx(context, 48); - Bitmap bitmap = FileHelper.getThumbnail(context, outputUri, new Point(px, px)); - return new BitmapDrawable(context.getResources(), bitmap); - } + Uri outputUri = result.getOutputUris().get(i); + if (mIconCache.containsKey(outputUri)) { + continue; + } - final Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(outputUri, type); + OpenPgpMetadata metadata = result.mMetadata.get(i); + String type = metadata.getMimeType(); + + Drawable icon = null; + + if (ClipDescription.compareMimeTypes(type, "image/*")) { + int px = FormattingUtils.dpToPx(context, 48); + Bitmap bitmap = FileHelper.getThumbnail(context, outputUri, new Point(px, px)); + icon = new BitmapDrawable(context.getResources(), bitmap); + } else { + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(outputUri, type); + + final List matches = + context.getPackageManager().queryIntentActivities(intent, 0); + // noinspection LoopStatementThatDoesntLoop + for (ResolveInfo match : matches) { + icon = match.loadIcon(getActivity().getPackageManager()); + break; + } + } + + if (icon != null) { + mIconCache.put(outputUri, icon); + } - final List matches = - context.getPackageManager().queryIntentActivities(intent, 0); - //noinspection LoopStatementThatDoesntLoop - for (ResolveInfo match : matches) { - return match.loadIcon(getActivity().getPackageManager()); } return null; @@ -333,20 +350,14 @@ public class DecryptListFragment } @Override - protected void onPostExecute(Drawable icon) { - processResult(uri, icon); + protected void onPostExecute(Void v) { + InputDataResult result = mInputDataResults.get(uri); + mAdapter.addResult(uri, result); } }.execute(); } - private void processResult(final Uri uri, Drawable icon) { - - InputDataResult result = mInputDataResults.get(uri); - mAdapter.addResult(uri, result, icon); - - } - public void retryUri(Uri uri) { // never interrupt running operations! @@ -521,7 +532,6 @@ public class DecryptListFragment public class ViewModel { Uri mInputUri; InputDataResult mResult; - Drawable mIcon; int mProgress, mMax; String mProgressMsg; @@ -538,10 +548,6 @@ public class DecryptListFragment mResult = result; } - void addIcon(Drawable icon) { - mIcon = icon; - } - boolean hasResult() { return mResult != null; } @@ -671,11 +677,11 @@ public class DecryptListFragment holder.vFilesize.setText(FileHelper.readableFileSize(size)); } - if (model.mIcon != null) { - holder.vThumbnail.setImageDrawable(model.mIcon); - } else { + // if (model.mIcon != null) { + // holder.vThumbnail.setImageDrawable(model.mIcon); + //} else { holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); - } + //} holder.vFile.setOnClickListener(new OnClickListener() { @Override @@ -787,16 +793,13 @@ public class DecryptListFragment notifyItemChanged(pos); } - public void addResult(Uri uri, InputDataResult result, Drawable icon) { + public void addResult(Uri uri, InputDataResult result) { ViewModel model = new ViewModel(uri); int pos = mDataset.indexOf(model); model = mDataset.get(pos); model.addResult(result); - if (icon != null) { - model.addIcon(icon); - } notifyItemChanged(pos); } From 81ce075df9e656c662c00c603388e950013c384a Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 02:19:23 +0200 Subject: [PATCH 11/34] multidecrypt: prepare viewholder for file list --- .../keychain/ui/DecryptListFragment.java | 100 +++++++++------- .../main/res/layout/decrypt_list_entry.xml | 111 ++++++++++-------- 2 files changed, 119 insertions(+), 92 deletions(-) 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 27ad9cbb1..bc3319617 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -46,6 +46,7 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.PopupMenu.OnDismissListener; import android.widget.PopupMenu.OnMenuItemClickListener; @@ -61,6 +62,7 @@ import org.sufficientlysecure.keychain.operations.results.InputDataResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.InputDataParcel; +import org.sufficientlysecure.keychain.ui.DecryptListFragment.ViewHolder.SubViewHolder; import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; // this import NEEDS to be above the ViewModel one, or it won't compile! (as of 06/06/15) import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; @@ -659,38 +661,43 @@ public class DecryptListFragment final OpenPgpMetadata metadata = model.mResult.mMetadata.get(0); - String filename; - if (metadata == null) { - filename = getString(R.string.filename_unknown); - } else if (TextUtils.isEmpty(metadata.getFilename())) { - filename = getString("text/plain".equals(metadata.getMimeType()) - ? R.string.filename_unknown_text : R.string.filename_unknown); - } else { - filename = metadata.getFilename(); - } - holder.vFilename.setText(filename); + { + SubViewHolder fileHolder = holder.mFileHolderList.get(0); - long size = metadata == null ? 0 : metadata.getOriginalSize(); - if (size == -1 || size == 0) { - holder.vFilesize.setText(""); - } else { - holder.vFilesize.setText(FileHelper.readableFileSize(size)); - } - - // if (model.mIcon != null) { - // holder.vThumbnail.setImageDrawable(model.mIcon); - //} else { - holder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); - //} - - holder.vFile.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - if (model.mResult.success()) { - displayWithViewIntent(model.mResult, 0, false); - } + String filename; + if (metadata == null) { + filename = getString(R.string.filename_unknown); + } else if (TextUtils.isEmpty(metadata.getFilename())) { + filename = getString("text/plain".equals(metadata.getMimeType()) + ? R.string.filename_unknown_text : R.string.filename_unknown); + } else { + filename = metadata.getFilename(); } - }); + fileHolder.vFilename.setText(filename); + + long size = metadata == null ? 0 : metadata.getOriginalSize(); + if (size == -1 || size == 0) { + fileHolder.vFilesize.setText(""); + } else { + fileHolder.vFilesize.setText(FileHelper.readableFileSize(size)); + } + + // if (model.mIcon != null) { + // holder.vThumbnail.setImageDrawable(model.mIcon); + //} else { + fileHolder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); + //} + + fileHolder.vFile.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (model.mResult.success()) { + displayWithViewIntent(model.mResult, 0, false); + } + } + }); + + } OpenPgpSignatureResult sigResult = model.mResult.mDecryptVerifyResult.getSignatureResult(); if (sigResult != null) { @@ -816,11 +823,6 @@ public class DecryptListFragment public ProgressBar vProgress; public TextView vProgressMsg; - public View vFile; - public TextView vFilename; - public TextView vFilesize; - public ImageView vThumbnail; - public ImageView vEncStatusIcon; public TextView vEncStatusText; @@ -837,6 +839,24 @@ public class DecryptListFragment public ImageView vCancelledRetry; + public LinearLayout vFileList; + + public static class SubViewHolder { + public View vFile; + public TextView vFilename; + public TextView vFilesize; + public ImageView vThumbnail; + + public SubViewHolder(View itemView) { + vFile = itemView.findViewById(R.id.file); + vFilename = (TextView) itemView.findViewById(R.id.filename); + vFilesize = (TextView) itemView.findViewById(R.id.filesize); + vThumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); + } + } + + public ArrayList mFileHolderList = new ArrayList<>(); + public ViewHolder(View itemView) { super(itemView); @@ -845,11 +865,6 @@ public class DecryptListFragment vProgress = (ProgressBar) itemView.findViewById(R.id.progress); vProgressMsg = (TextView) itemView.findViewById(R.id.progress_msg); - vFile = itemView.findViewById(R.id.file); - vFilename = (TextView) itemView.findViewById(R.id.filename); - vFilesize = (TextView) itemView.findViewById(R.id.filesize); - vThumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); - vEncStatusIcon = (ImageView) itemView.findViewById(R.id.result_encryption_icon); vEncStatusText = (TextView) itemView.findViewById(R.id.result_encryption_text); @@ -860,6 +875,11 @@ public class DecryptListFragment vSignatureMail= (TextView) itemView.findViewById(R.id.result_signature_email); vSignatureAction = (TextView) itemView.findViewById(R.id.result_signature_action); + vFileList = (LinearLayout) itemView.findViewById(R.id.file_list); + for (int i = 0; i < vFileList.getChildCount(); i++) { + mFileHolderList.add(new SubViewHolder(vFileList.getChildAt(i))); + } + vContextMenu = itemView.findViewById(R.id.context_menu); vErrorMsg = (TextView) itemView.findViewById(R.id.result_error_msg); diff --git a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml index 048595dd8..7b9515170 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml @@ -24,7 +24,7 @@ android:outAnimation="@anim/fade_out" android:id="@+id/view_animator" android:measureAllChildren="false" - custom:initialView="0" + custom:initialView="1" android:minHeight="?listPreferredItemHeightSmall" android:animateLayoutChanges="true" > @@ -184,62 +184,69 @@ - - + android:id="@+id/file_list"> - - - - - - - - + > + + + + + + + + + + + + + + From 24f2a9468c2495d312aa5a8d05273ea644ab6da8 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 02:56:14 +0200 Subject: [PATCH 12/34] multidecrypt: display all outputUris as individual items --- .../operations/results/InputDataResult.java | 2 +- .../keychain/ui/DecryptListFragment.java | 56 ++++++++++++---- .../main/res/layout/decrypt_list_entry.xml | 65 +------------------ .../res/layout/decrypt_list_file_item.xml | 63 ++++++++++++++++++ 4 files changed, 109 insertions(+), 77 deletions(-) create mode 100644 OpenKeychain/src/main/res/layout/decrypt_list_file_item.xml diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java index 0e4bddb4c..56e99ba1b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/InputDataResult.java @@ -51,7 +51,7 @@ public class InputDataResult extends InputPendingResult { @NonNull ArrayList outputUris, @NonNull ArrayList metadata) { super(result, log); mDecryptVerifyResult = decryptResult; - if (outputUris.size() == metadata.size()) { + if (outputUris.size() != metadata.size()) { throw new AssertionError("number of output URIs must match metadata!"); } mOutputUris = outputUris; 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 bc3319617..f7acd2f1c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -62,10 +62,10 @@ import org.sufficientlysecure.keychain.operations.results.InputDataResult; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.InputDataParcel; -import org.sufficientlysecure.keychain.ui.DecryptListFragment.ViewHolder.SubViewHolder; import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; -// 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 AND SubViewHolder one, or it won't compile! (as of 16.09.15) import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.StatusHolder; +import org.sufficientlysecure.keychain.ui.DecryptListFragment.ViewHolder.SubViewHolder; import org.sufficientlysecure.keychain.ui.DecryptListFragment.DecryptFilesAdapter.ViewModel; import org.sufficientlysecure.keychain.ui.adapter.SpacesItemDecoration; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; @@ -395,7 +395,7 @@ public class DecryptListFragment String plaintext = FileHelper.readTextFromUri(activity, outputUri, null); Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType(metadata.getMimeType()); + intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, plaintext); startActivity(intent); @@ -408,8 +408,8 @@ public class DecryptListFragment Intent intent = new Intent(activity, DisplayTextActivity.class); intent.setAction(Intent.ACTION_VIEW); - intent.setDataAndType(outputUri, metadata.getMimeType()); - intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result); + intent.setDataAndType(outputUri, "text/plain"); + intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result.mDecryptVerifyResult); activity.startActivity(intent); } else { @@ -659,10 +659,13 @@ public class DecryptListFragment KeyFormattingUtils.setStatus(getResources(), holder, model.mResult.mDecryptVerifyResult); - final OpenPgpMetadata metadata = model.mResult.mMetadata.get(0); + int numFiles = model.mResult.getOutputUris().size(); + holder.resizeFileList(numFiles, LayoutInflater.from(getActivity())); + for (int i = 0; i < numFiles; i++) { - { - SubViewHolder fileHolder = holder.mFileHolderList.get(0); + Uri outputUri = model.mResult.getOutputUris().get(i); + OpenPgpMetadata metadata = model.mResult.mMetadata.get(i); + SubViewHolder fileHolder = holder.mFileHolderList.get(i); String filename; if (metadata == null) { @@ -682,17 +685,19 @@ public class DecryptListFragment fileHolder.vFilesize.setText(FileHelper.readableFileSize(size)); } - // if (model.mIcon != null) { - // holder.vThumbnail.setImageDrawable(model.mIcon); - //} else { - fileHolder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); - //} + if (mIconCache.containsKey(outputUri)) { + fileHolder.vThumbnail.setImageDrawable(mIconCache.get(outputUri)); + } else { + fileHolder.vThumbnail.setImageResource(R.drawable.ic_doc_generic_am); + } + // save index closure-style :) + final int idx = i; fileHolder.vFile.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { if (model.mResult.success()) { - displayWithViewIntent(model.mResult, 0, false); + displayWithViewIntent(model.mResult, idx, false); } } }); @@ -856,6 +861,7 @@ public class DecryptListFragment } public ArrayList mFileHolderList = new ArrayList<>(); + private int mCurrentFileListSize = 0; public ViewHolder(View itemView) { super(itemView); @@ -878,6 +884,7 @@ public class DecryptListFragment vFileList = (LinearLayout) itemView.findViewById(R.id.file_list); for (int i = 0; i < vFileList.getChildCount(); i++) { mFileHolderList.add(new SubViewHolder(vFileList.getChildAt(i))); + mCurrentFileListSize += 1; } vContextMenu = itemView.findViewById(R.id.context_menu); @@ -889,6 +896,27 @@ public class DecryptListFragment } + public void resizeFileList(int size, LayoutInflater inflater) { + int childCount = vFileList.getChildCount(); + // if we require more children, create them + while (childCount < size) { + View v = inflater.inflate(R.layout.decrypt_list_file_item, null); + vFileList.addView(v); + mFileHolderList.add(new SubViewHolder(v)); + childCount += 1; + } + + while (size < mCurrentFileListSize) { + mCurrentFileListSize -= 1; + vFileList.getChildAt(mCurrentFileListSize).setVisibility(View.GONE); + } + while (size > mCurrentFileListSize) { + vFileList.getChildAt(mCurrentFileListSize).setVisibility(View.VISIBLE); + mCurrentFileListSize += 1; + } + + } + @Override public ImageView getEncryptionStatusIcon() { return vEncStatusIcon; diff --git a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml index 7b9515170..e6ee67580 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml @@ -184,69 +184,10 @@ + android:id="@+id/file_list" + android:orientation="vertical"> - - - - - - - - - - - - - - - + diff --git a/OpenKeychain/src/main/res/layout/decrypt_list_file_item.xml b/OpenKeychain/src/main/res/layout/decrypt_list_file_item.xml new file mode 100644 index 000000000..b37ac1013 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/decrypt_list_file_item.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + \ No newline at end of file From ce105d955fff57d8e7b90acbf823d66567e89290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 31 Aug 2015 20:25:48 +0200 Subject: [PATCH 13/34] Add text icon in decrypt list Conflicts: OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java --- Graphics/get-material-icons.sh | 1 + .../keychain/ui/DecryptListFragment.java | 4 +++- .../main/res/drawable-hdpi/ic_chat_black_24dp.png | Bin 0 -> 158 bytes .../main/res/drawable-mdpi/ic_chat_black_24dp.png | Bin 0 -> 129 bytes .../res/drawable-xhdpi/ic_chat_black_24dp.png | Bin 0 -> 193 bytes .../res/drawable-xxhdpi/ic_chat_black_24dp.png | Bin 0 -> 248 bytes .../res/drawable-xxxhdpi/ic_chat_black_24dp.png | Bin 0 -> 316 bytes 7 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 OpenKeychain/src/main/res/drawable-hdpi/ic_chat_black_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-mdpi/ic_chat_black_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xhdpi/ic_chat_black_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxhdpi/ic_chat_black_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxxhdpi/ic_chat_black_24dp.png diff --git a/Graphics/get-material-icons.sh b/Graphics/get-material-icons.sh index 67370afc2..d4d72e5b3 100755 --- a/Graphics/get-material-icons.sh +++ b/Graphics/get-material-icons.sh @@ -44,6 +44,7 @@ python copy OpenKeychain social grey person 48 python copy OpenKeychain communication grey email 24 python copy OpenKeychain social black share 24 python copy OpenKeychain content black content_copy 24 +python copy OpenKeychain communication black chat 24 # navigation drawer sections python copy OpenKeychain communication black vpn_key 24 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 f7acd2f1c..cc7e0253c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -324,7 +324,9 @@ public class DecryptListFragment Drawable icon = null; - if (ClipDescription.compareMimeTypes(type, "image/*")) { + if (ClipDescription.compareMimeTypes(type, "text/plain")) { + icon = getResources().getDrawable(R.drawable.ic_chat_black_24dp); + } else if (ClipDescription.compareMimeTypes(type, "image/*")) { int px = FormattingUtils.dpToPx(context, 48); Bitmap bitmap = FileHelper.getThumbnail(context, outputUri, new Point(px, px)); icon = new BitmapDrawable(context.getResources(), bitmap); diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_chat_black_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_chat_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..fa4d2c14d544db46351e1a83ffa4b8362ce29971 GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8s;7%%NCo5DOB;Dx4R~BHMn7Hu zmQ$%RsGHMiE|bn7ZG%6*e+5T8`}?<`sjyJmFZq;Yck!|$Nv>tberY*A5pDirzNscO zKjo*~rwO48YwkBYv6ixml-v?bx#M|sK~jL>3JJd%_1003y%_dgZl4CUkipZ{&t;uc GLK6UvUOBt~ literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_chat_black_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_chat_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d239cfa099e80ce6d8d81fb69fe6e3ecf636235f GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*14^J1zkP61+1;QLD6(>K;XQ(;) zfIpl+%uiyUOU4g>l_fFEd=8u11fQy8WgloMx&DD^-8_${`(8*%b)2(bqLr=?YRMe% cQ-O(LXAw*39nJ3tfMzmyy85}Sb4q9e04}I1DgXcg literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_chat_black_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_chat_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..e9e92e5f809cd0a62b9a5867d57319d1a86fe31e GIT binary patch literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0D4o?@ykP61Pmo9QL849>wjP~7f zM%N=^>jsTu{-rtzEdq0ncg?i^|K5SaK6KmT6TH7ay_<9M@#2DmP49knS|l`<9}0DD zYgKAkb^+$!~a{p z33yLgpt{8Bpa$Do7l9`B(zOX3PLpb9ec%82K)F=fdCTc5EDO~A_pqy%t&v!uu0Y{H?S=IdQwvf_WR%cb+&D=glaYav?%8WrE{BXXgdq*EbwH zrO<4Uv9SI`f8kDr<~DwwS#@lZDFKaNvlfK$hGaKdO%htMn29&Q-`lt8y(8~Yp~nl3 wIrcV7v77Q`$y~8mQ5XOeXk>eS=l*xb9SQ35SK7Hw0eXeO)78&qol`;+0JGy`h5!Hn literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_chat_black_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_chat_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..55d42e28499517f1c528b181ffc0e5d70779ef44 GIT binary patch literal 316 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z{u?B;uuoF`1bsH-@^eCEFX7H zj0+I)TEaS|LGUPpX#t0qMVb9;6R)F z)<=DA`dg0auKxL5{srrIi|GZ<`nwt>=UzU=Bqgo3dy)7()5*3|*=&q&dS@Ouk)pi! zi<&i;fP%w;Nx~aSnBuq@SU3a}790p-WV-jyI^iXV=WrmC@%@)w!T||PbvG|DFfy^! z=x_@tB;4c#$~g#sILp%WvZ2;0VK#5Zn$3*X9&C4tr*UVzd3JaCov#yZqpva4GwxVY z^YFXpe*a&!m)=jVoB!qalKbj^?Ovh?soXU;HvY|GknLt);3PQ@7%B{&u6{1-oD!M< DBXoja literal 0 HcmV?d00001 From 97c55ee4bb519236027edc973851e7bbc52c97f3 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 17:47:16 +0200 Subject: [PATCH 14/34] multidecrypt: context menu per card, not per file --- .../keychain/ui/DecryptListFragment.java | 9 ++++++--- .../src/main/res/layout/decrypt_list_entry.xml | 12 +++++++++++- .../src/main/res/layout/decrypt_list_file_item.xml | 10 +--------- .../src/main/res/menu/decrypt_item_context_menu.xml | 12 ------------ 4 files changed, 18 insertions(+), 25 deletions(-) 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 cc7e0253c..5f6089fd9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -325,6 +325,7 @@ public class DecryptListFragment Drawable icon = null; if (ClipDescription.compareMimeTypes(type, "text/plain")) { + // noinspection deprecation, this should be called from Context, but not available in minSdk icon = getResources().getDrawable(R.drawable.ic_chat_black_24dp); } else if (ClipDescription.compareMimeTypes(type, "image/*")) { int px = FormattingUtils.dpToPx(context, 48); @@ -478,6 +479,10 @@ public class DecryptListFragment intent.putExtra(LogDisplayFragment.EXTRA_RESULT, model.mResult); activity.startActivity(intent); return true; + case R.id.decrypt_delete: + deleteFile(activity, model.mInputUri); + return true; + /* case R.id.decrypt_share: displayWithViewIntent(model.mResult, 0, true); return true; @@ -490,9 +495,7 @@ public class DecryptListFragment FileHelper.saveDocument(this, metadata.getFilename(), model.mInputUri, metadata.getMimeType(), R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to, REQUEST_CODE_OUTPUT); return true; - case R.id.decrypt_delete: - deleteFile(activity, model.mInputUri); - return true; + */ } return false; } diff --git a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml index e6ee67580..47a8d85c4 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml @@ -78,14 +78,24 @@ + + + - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/menu/decrypt_item_context_menu.xml b/OpenKeychain/src/main/res/menu/decrypt_item_context_menu.xml index ab526c4a5..65b8f210d 100644 --- a/OpenKeychain/src/main/res/menu/decrypt_item_context_menu.xml +++ b/OpenKeychain/src/main/res/menu/decrypt_item_context_menu.xml @@ -7,18 +7,6 @@ android:icon="@drawable/ic_view_list_grey_24dp" /> - - - - Date: Wed, 16 Sep 2015 17:47:44 +0200 Subject: [PATCH 15/34] enable view intent with text/plain (related #290) --- OpenKeychain/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 63e1a5ce7..c966d688a 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -256,7 +256,7 @@ This links to attached asc files in AOSP mail. It is deactivated because of https://github.com/open-keychain/open-keychain/issues/290 --> - + From ece06b1933c26688d2eb6b7fa8657acbb8833728 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 19:33:43 +0200 Subject: [PATCH 16/34] multidecrypt: use bottom sheet for longclick options --- OpenKeychain/build.gradle | 1 + .../operations/InputDataOperation.java | 8 +- .../keychain/ui/DecryptListFragment.java | 78 ++++++++++++++++++- .../ui/base/CryptoOperationFragment.java | 8 +- .../main/res/layout/decrypt_list_entry.xml | 4 +- .../main/res/menu/decrypt_bottom_sheet.xml | 19 +++++ 6 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 OpenKeychain/src/main/res/menu/decrypt_bottom_sheet.xml diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index c8b095eac..9d29d0a98 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -61,6 +61,7 @@ dependencies { compile 'org.apache.james:apache-mime4j-core:0.7.2' compile 'org.apache.james:apache-mime4j-dom:0.7.2' compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0' + compile 'com.cocosw:bottomsheet:1.1.1@aar' // libs as submodules compile project(':extern:openpgp-api-lib:openpgp-api') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index 14f711df0..a5208e05b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -156,7 +156,6 @@ public class InputDataOperation extends BaseOperation { if (mFilename != null) { log.add(LogType.MSG_DATA_MIME_FILENAME, 3, mFilename); } - log.add(LogType.MSG_DATA_MIME_LENGTH, 3, bd.getContentLength()); Uri uri = TemporaryStorageProvider.createFile(mContext, mFilename, bd.getMimeType()); OutputStream out = mContext.getContentResolver().openOutputStream(uri, "w"); @@ -165,12 +164,15 @@ public class InputDataOperation extends BaseOperation { throw new IOException("Error getting file for writing!"); } - int len; + int len, totalLength = 0; while ((len = is.read(buf)) > 0) { + totalLength += len; out.write(buf, 0, len); } - OpenPgpMetadata metadata = new OpenPgpMetadata(mFilename, bd.getMimeType(), 0L, bd.getContentLength()); + log.add(LogType.MSG_DATA_MIME_LENGTH, 3, totalLength); + + OpenPgpMetadata metadata = new OpenPgpMetadata(mFilename, bd.getMimeType(), 0L, totalLength); out.close(); outputUris.add(uri); 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 5f6089fd9..98d8c1ba8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -44,7 +44,9 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; import android.view.ViewGroup; +import android.webkit.MimeTypeMap; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupMenu; @@ -54,6 +56,7 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.ViewAnimator; +import com.cocosw.bottomsheet.BottomSheet; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; import org.sufficientlysecure.keychain.Constants; @@ -97,6 +100,7 @@ public class DecryptListFragment private Uri mCurrentInputUri; private DecryptFilesAdapter mAdapter; + private Uri mCurrentSaveFileUri; /** * Creates new instance of this fragment @@ -222,9 +226,8 @@ public class DecryptListFragment case REQUEST_CODE_OUTPUT: { // This happens after output file was selected, so start our operation if (resultCode == Activity.RESULT_OK && data != null) { - Uri decryptedFileUri = mInputDataResults.get(mCurrentInputUri).getOutputUris().get(0); Uri saveUri = data.getData(); - saveFile(decryptedFileUri, saveUri); + saveFile(saveUri); mCurrentInputUri = null; } return; @@ -236,7 +239,37 @@ public class DecryptListFragment } } - private void saveFile(Uri decryptedFileUri, Uri saveUri) { + private void saveFileDialog(InputDataResult result, int index) { + + Activity activity = getActivity(); + if (activity == null) { + return; + } + + OpenPgpMetadata metadata = result.mMetadata.get(index); + Uri saveUri = Uri.fromFile(activity.getExternalFilesDir(metadata.getMimeType())); + mCurrentSaveFileUri = result.getOutputUris().get(index); + + String filename = metadata.getFilename(); + if (filename == null) { + String ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(metadata.getMimeType()); + filename = "decrypted" + (ext != null ? "."+ext : ""); + } + + FileHelper.saveDocument(this, filename, saveUri, metadata.getMimeType(), + R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to, REQUEST_CODE_OUTPUT); + } + + private void saveFile(Uri saveUri) { + if (mCurrentSaveFileUri == null) { + return; + } + + Uri decryptedFileUri = mCurrentSaveFileUri; + mCurrentInputUri = null; + + hideKeyboard(); + Activity activity = getActivity(); if (activity == null) { return; @@ -379,6 +412,33 @@ public class DecryptListFragment } + public void displayBottomSheet(final InputDataResult result, final int index) { + + Activity activity = getActivity(); + if (activity == null) { + return; + } + + new BottomSheet.Builder(activity).sheet(R.menu.decrypt_bottom_sheet).listener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.decrypt_open: + displayWithViewIntent(result, index, false); + break; + case R.id.decrypt_share: + displayWithViewIntent(result, index, true); + break; + case R.id.decrypt_save: + saveFileDialog(result, index); + break; + } + return false; + } + }).grid().show(); + + } + public void displayWithViewIntent(InputDataResult result, int index, boolean share) { Activity activity = getActivity(); if (activity == null) { @@ -698,6 +758,18 @@ public class DecryptListFragment // save index closure-style :) final int idx = i; + + fileHolder.vFile.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + if (model.mResult.success()) { + displayBottomSheet(model.mResult, idx); + return true; + } + return false; + } + }); + fileHolder.vFile.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java index de90d48fd..88351b6b7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationFragment.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.base; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Parcelable; @@ -116,14 +117,15 @@ public abstract class CryptoOperationFragment + android:orientation="horizontal" + style="?listPreferredItemHeight" + > + + + + + + + + + From 6624d1f8304a07ac43e7f22a138262bba9782758 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 19:54:57 +0200 Subject: [PATCH 17/34] mime: respect charset header (default to utf-8) --- .../keychain/operations/InputDataOperation.java | 8 +++++++- .../operations/results/DecryptVerifyResult.java | 11 ----------- .../keychain/pgp/PgpDecryptVerifyOperation.java | 7 +++---- .../keychain/remote/OpenPgpService.java | 5 ++--- .../keychain/ui/DecryptListFragment.java | 3 ++- .../keychain/ui/DisplayTextActivity.java | 8 +++++--- .../keychain/pgp/PgpEncryptDecryptTest.java | 4 ++-- 7 files changed, 21 insertions(+), 25 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index a5208e05b..5e932ffc9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -172,7 +172,13 @@ public class InputDataOperation extends BaseOperation { log.add(LogType.MSG_DATA_MIME_LENGTH, 3, totalLength); - OpenPgpMetadata metadata = new OpenPgpMetadata(mFilename, bd.getMimeType(), 0L, totalLength); + String charset = bd.getCharset(); + // the charset defaults to us-ascii, but we want to default to utf-8 + if ("us-ascii".equals(charset)) { + charset = "utf-8"; + } + + OpenPgpMetadata metadata = new OpenPgpMetadata(mFilename, bd.getMimeType(), 0L, totalLength, charset); out.close(); outputUris.add(uri); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java index e8be9fa78..95cf179af 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/DecryptVerifyResult.java @@ -34,9 +34,6 @@ public class DecryptVerifyResult extends InputPendingResult { OpenPgpSignatureResult mSignatureResult; OpenPgpDecryptionResult mDecryptionResult; OpenPgpMetadata mDecryptionMetadata; - // This holds the charset which was specified in the ascii armor, if specified - // https://tools.ietf.org/html/rfc4880#page56 - String mCharset; CryptoInputParcel mCachedCryptoInputParcel; @@ -96,14 +93,6 @@ public class DecryptVerifyResult extends InputPendingResult { mDecryptionMetadata = decryptMetadata; } - public String getCharset () { - return mCharset; - } - - public void setCharset(String charset) { - mCharset = charset; - } - public void setOutputBytes(byte[] outputBytes) { mOutputBytes = outputBytes; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index dda15f382..007f686e8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -556,12 +556,12 @@ public class PgpDecryptVerifyOperation extends BaseOperation= 4) { - OpenPgpMetadata metadata = pgpResult.getDecryptionMetadata(); if (metadata != null) { result.putExtra(OpenPgpApi.RESULT_METADATA, metadata); } } - String charset = pgpResult.getCharset(); + String charset = metadata != null ? metadata.getCharset() : null; if (charset != null) { result.putExtra(OpenPgpApi.RESULT_CHARSET, charset); } 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 98d8c1ba8..af1108488 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -472,7 +472,8 @@ public class DecryptListFragment Intent intent = new Intent(activity, DisplayTextActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(outputUri, "text/plain"); - intent.putExtra(DisplayTextActivity.EXTRA_METADATA, result.mDecryptVerifyResult); + intent.putExtra(DisplayTextActivity.EXTRA_RESULT, result.mDecryptVerifyResult); + intent.putExtra(DisplayTextActivity.EXTRA_METADATA, metadata); activity.startActivity(intent); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java index 3c8e972b9..4bcca09f1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextActivity.java @@ -25,9 +25,9 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.view.View; import android.widget.Toast; +import org.openintents.openpgp.OpenPgpMetadata; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.ui.base.BaseActivity; @@ -35,6 +35,7 @@ import org.sufficientlysecure.keychain.util.FileHelper; public class DisplayTextActivity extends BaseActivity { + public static final String EXTRA_RESULT = "result"; public static final String EXTRA_METADATA = "metadata"; @Override @@ -60,11 +61,12 @@ public class DisplayTextActivity extends BaseActivity { return; } - DecryptVerifyResult result = intent.getParcelableExtra(EXTRA_METADATA); + DecryptVerifyResult result = intent.getParcelableExtra(EXTRA_RESULT); + OpenPgpMetadata metadata = intent.getParcelableExtra(EXTRA_METADATA); String plaintext; try { - plaintext = FileHelper.readTextFromUri(this, intent.getData(), result.getCharset()); + plaintext = FileHelper.readTextFromUri(this, intent.getData(), metadata.getCharset()); } catch (IOException e) { Toast.makeText(this, R.string.error_preparing_data, Toast.LENGTH_LONG).show(); return; diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index 727464429..cc5ef8038 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -792,9 +792,9 @@ public class PgpEncryptDecryptTest { Assert.assertArrayEquals("decrypted ciphertext should equal plaintext bytes", out.toByteArray(), plaindata); Assert.assertEquals("charset should be read correctly", - "iso-2022-jp", result.getCharset()); + "iso-2022-jp", result.getDecryptionMetadata().getCharset()); Assert.assertEquals("decrypted ciphertext should equal plaintext", - new String(out.toByteArray(), result.getCharset()), plaintext); + new String(out.toByteArray(), result.getDecryptionMetadata().getCharset()), plaintext); Assert.assertEquals("decryptionResult should be RESULT_ENCRYPTED", OpenPgpDecryptionResult.RESULT_ENCRYPTED, result.getDecryptionResult().getResult()); Assert.assertEquals("signatureResult should be RESULT_NO_SIGNATURE", From 38a27855a98752dfaf8c10cba8e14e543970c390 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 20:07:04 +0200 Subject: [PATCH 18/34] multidecrypt: disable delete if not from ACTION_VIEW --- .../keychain/ui/DecryptActivity.java | 12 ++++--- .../keychain/ui/DecryptListFragment.java | 31 +++++++------------ 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 881190ae2..4f3f6cc6e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -82,6 +82,9 @@ public class DecryptActivity extends BaseActivity { return; } + // depending on the data source, we may or may not be able to delete the original file + boolean canDelete = false; + try { switch (action) { @@ -152,8 +155,9 @@ public class DecryptActivity extends BaseActivity { } // for everything else, just work on the intent data - case OpenKeychainIntents.DECRYPT_DATA: case Intent.ACTION_VIEW: + canDelete = true; + case OpenKeychainIntents.DECRYPT_DATA: default: uris.add(intent.getData()); @@ -173,7 +177,7 @@ public class DecryptActivity extends BaseActivity { return; } - displayListFragment(uris); + displayListFragment(uris, canDelete); } @@ -193,9 +197,9 @@ public class DecryptActivity extends BaseActivity { return tempFile; } - public void displayListFragment(ArrayList inputUris) { + public void displayListFragment(ArrayList inputUris, boolean canDelete) { - DecryptListFragment frag = DecryptListFragment.newInstance(inputUris); + DecryptListFragment frag = DecryptListFragment.newInstance(inputUris, canDelete); FragmentManager fragMan = getSupportFragmentManager(); 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 af1108488..5d6bc5e21 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -88,6 +88,7 @@ public class DecryptListFragment public static final String ARG_OUTPUT_URIS = "output_uris"; public static final String ARG_CANCELLED_URIS = "cancelled_uris"; public static final String ARG_RESULTS = "results"; + public static final String ARG_CAN_DELETE = "can_delete"; private static final int REQUEST_CODE_OUTPUT = 0x00007007; public static final String ARG_CURRENT_URI = "current_uri"; @@ -98,6 +99,7 @@ public class DecryptListFragment private ArrayList mCancelledInputUris; private Uri mCurrentInputUri; + private boolean mCanDelete; private DecryptFilesAdapter mAdapter; private Uri mCurrentSaveFileUri; @@ -105,11 +107,12 @@ public class DecryptListFragment /** * Creates new instance of this fragment */ - public static DecryptListFragment newInstance(ArrayList uris) { + public static DecryptListFragment newInstance(ArrayList uris, boolean canDelete) { DecryptListFragment frag = new DecryptListFragment(); Bundle args = new Bundle(); args.putParcelableArrayList(ARG_INPUT_URIS, uris); + args.putBoolean(ARG_CAN_DELETE, canDelete); frag.setArguments(args); return frag; @@ -135,7 +138,7 @@ public class DecryptListFragment vFilesList.setLayoutManager(new LinearLayoutManager(getActivity())); vFilesList.setItemAnimator(new DefaultItemAnimator()); - mAdapter = new DecryptFilesAdapter(this); + mAdapter = new DecryptFilesAdapter(); vFilesList.setAdapter(mAdapter); return view; @@ -162,6 +165,7 @@ public class DecryptListFragment outState.putParcelable(ARG_OUTPUT_URIS, new ParcelableHashMap<>(mInputDataResults)); outState.putParcelableArrayList(ARG_CANCELLED_URIS, mCancelledInputUris); outState.putParcelable(ARG_CURRENT_URI, mCurrentInputUri); + outState.putBoolean(ARG_CAN_DELETE, mCanDelete); } @@ -176,6 +180,8 @@ public class DecryptListFragment ParcelableHashMap results = args.getParcelable(ARG_RESULTS); Uri currentInputUri = args.getParcelable(ARG_CURRENT_URI); + mCanDelete = args.getBoolean(ARG_CAN_DELETE, false); + displayInputUris(inputUris, currentInputUri, cancelledUris, results != null ? results.getMap() : null ); @@ -543,20 +549,6 @@ public class DecryptListFragment case R.id.decrypt_delete: deleteFile(activity, model.mInputUri); return true; - /* - case R.id.decrypt_share: - displayWithViewIntent(model.mResult, 0, true); - return true; - case R.id.decrypt_save: - OpenPgpMetadata metadata = model.mResult.mDecryptVerifyResult.getDecryptionMetadata(); - if (metadata == null) { - return true; - } - mCurrentInputUri = model.mInputUri; - FileHelper.saveDocument(this, metadata.getFilename(), model.mInputUri, metadata.getMimeType(), - R.string.title_decrypt_to_file, R.string.specify_file_to_decrypt_to, REQUEST_CODE_OUTPUT); - return true; - */ } return false; } @@ -594,7 +586,6 @@ public class DecryptListFragment public class DecryptFilesAdapter extends RecyclerView.Adapter { private ArrayList mDataset; - private OnMenuItemClickListener mMenuItemClickListener; private ViewModel mMenuClickedModel; public class ViewModel { @@ -659,8 +650,7 @@ public class DecryptListFragment } // Provide a suitable constructor (depends on the kind of dataset) - public DecryptFilesAdapter(OnMenuItemClickListener menuItemClickListener) { - mMenuItemClickListener = menuItemClickListener; + public DecryptFilesAdapter() { mDataset = new ArrayList<>(); } @@ -812,13 +802,14 @@ public class DecryptListFragment mMenuClickedModel = model; PopupMenu menu = new PopupMenu(activity, view); menu.inflate(R.menu.decrypt_item_context_menu); - menu.setOnMenuItemClickListener(mMenuItemClickListener); + menu.setOnMenuItemClickListener(DecryptListFragment.this); menu.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(PopupMenu popupMenu) { mMenuClickedModel = null; } }); + menu.getMenu().findItem(R.id.decrypt_delete).setEnabled(mCanDelete); menu.show(); } }); From 93421e902c10c778f7df01a0dbc8d7b3261d8dc2 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 20:36:06 +0200 Subject: [PATCH 19/34] mime: handle non-mime data, just pass it through --- .../operations/InputDataOperation.java | 51 +++++++++++++++---- .../operations/results/OperationResult.java | 1 + OpenKeychain/src/main/res/values/strings.xml | 1 + 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index 5e932ffc9..0bbbdeb31 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -62,14 +62,13 @@ public class InputDataOperation extends BaseOperation { @NonNull @Override - public InputDataResult execute(InputDataParcel input, - CryptoInputParcel cryptoInput) { + public InputDataResult execute(InputDataParcel input, CryptoInputParcel cryptoInput) { final OperationLog log = new OperationLog(); log.add(LogType.MSG_DATA, 0); - Uri currentUri; + Uri currentInputUri; DecryptVerifyResult decryptResult = null; @@ -83,8 +82,8 @@ public class InputDataOperation extends BaseOperation { decryptInput.setInputUri(input.getInputUri()); - currentUri = TemporaryStorageProvider.createFile(mContext); - decryptInput.setOutputUri(currentUri); + currentInputUri = TemporaryStorageProvider.createFile(mContext); + decryptInput.setOutputUri(currentInputUri); decryptResult = op.execute(decryptInput, cryptoInput); if (decryptResult.isPending()) { @@ -93,7 +92,7 @@ public class InputDataOperation extends BaseOperation { log.addByMerge(decryptResult, 2); } else { - currentUri = input.getInputUri(); + currentInputUri = input.getInputUri(); } // If we aren't supposed to attempt mime decode, we are done here @@ -106,7 +105,7 @@ public class InputDataOperation extends BaseOperation { log.add(LogType.MSG_DATA_SKIP_MIME, 1); ArrayList uris = new ArrayList<>(); - uris.add(currentUri); + uris.add(currentInputUri); ArrayList metadatas = new ArrayList<>(); metadatas.add(decryptResult.getDecryptionMetadata()); @@ -119,7 +118,7 @@ public class InputDataOperation extends BaseOperation { InputStream in; try { - in = mContext.getContentResolver().openInputStream(currentUri); + in = mContext.getContentResolver().openInputStream(currentInputUri); } catch (FileNotFoundException e) { log.add(LogType.MSG_DATA_ERROR_IO, 2); return new InputDataResult(InputDataResult.RESULT_ERROR, log); @@ -150,6 +149,12 @@ public class InputDataOperation extends BaseOperation { @Override public void body(BodyDescriptor bd, InputStream is) throws MimeException, IOException { + // we read first, no need to create an output file if nothing was read! + int len = is.read(buf); + if (len < 0) { + return; + } + log.add(LogType.MSG_DATA_MIME_PART, 2); log.add(LogType.MSG_DATA_MIME_TYPE, 3, bd.getMimeType()); @@ -164,11 +169,11 @@ public class InputDataOperation extends BaseOperation { throw new IOException("Error getting file for writing!"); } - int len, totalLength = 0; - while ((len = is.read(buf)) > 0) { + int totalLength = 0; + do { totalLength += len; out.write(buf, 0, len); - } + } while ((len = is.read(buf)) > 0); log.add(LogType.MSG_DATA_MIME_LENGTH, 3, totalLength); @@ -185,11 +190,35 @@ public class InputDataOperation extends BaseOperation { metadatas.add(metadata); } + + }); try { parser.parse(in); + + // if no mime data parsed, just return the raw data as fallback + if (outputUris.isEmpty()) { + + log.add(LogType.MSG_DATA_MIME_NONE, 2); + + OpenPgpMetadata metadata; + if (decryptResult != null) { + metadata = decryptResult.getDecryptionMetadata(); + } else { + // if we neither decrypted nor mime-decoded, should this be treated as an error? + // either way, we know nothing about the data + metadata = new OpenPgpMetadata(); + } + + outputUris.add(currentInputUri); + metadatas.add(metadata); + + log.add(LogType.MSG_DATA_OK, 1); + return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, outputUris, metadatas); + } + log.add(LogType.MSG_DATA_MIME_OK, 2); log.add(LogType.MSG_DATA_OK, 1); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 69e7d3d2d..70ff7b338 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -834,6 +834,7 @@ public abstract class OperationResult implements Parcelable { MSG_DATA_MIME_LENGTH (LogLevel.DEBUG, R.string.msg_data_mime_length), MSG_DATA_MIME (LogLevel.DEBUG, R.string.msg_data_mime), MSG_DATA_MIME_OK (LogLevel.INFO, R.string.msg_data_mime_ok), + MSG_DATA_MIME_NONE (LogLevel.DEBUG, R.string.msg_data_mime_none), MSG_DATA_MIME_PART (LogLevel.DEBUG, R.string.msg_data_mime_part), MSG_DATA_MIME_TYPE (LogLevel.DEBUG, R.string.msg_data_mime_type), MSG_DATA_OK (LogLevel.OK, R.string.msg_data_ok), diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index a1e21e661..eafd0b879 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1363,6 +1363,7 @@ "" "" "" + "" "" "" "" From 4722ecb9557ff9659cf5f6319139624bc64a5da5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 20:36:19 +0200 Subject: [PATCH 20/34] actually use cleaned text if we read from clipboard --- .../sufficientlysecure/keychain/ui/DecryptActivity.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 4f3f6cc6e..043929130 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -181,9 +181,13 @@ public class DecryptActivity extends BaseActivity { } - @Nullable public Uri readToTempFile(String text) throws IOException { + @Nullable + public Uri readToTempFile(String text) throws IOException { Uri tempFile = TemporaryStorageProvider.createFile(this); OutputStream outStream = getContentResolver().openOutputStream(tempFile); + if (outStream == null) { + return null; + } // clean up ascii armored message, fixing newlines and stuff String cleanedText = PgpHelper.getPgpContent(text); @@ -192,7 +196,7 @@ public class DecryptActivity extends BaseActivity { } // if cleanup didn't work, just try the raw data - outStream.write(text.getBytes()); + outStream.write(cleanedText.getBytes()); outStream.close(); return tempFile; } From 01038d6a2640ce3c6327389798077bb4b35a6c9b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 20:44:32 +0200 Subject: [PATCH 21/34] mime: add logging messages --- .../operations/InputDataOperation.java | 35 +++++++++---------- .../operations/results/OperationResult.java | 2 +- OpenKeychain/src/main/res/values/strings.xml | 26 +++++++------- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index 0bbbdeb31..99bd0ebef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -75,7 +75,7 @@ public class InputDataOperation extends BaseOperation { PgpDecryptVerifyInputParcel decryptInput = input.getDecryptInput(); if (decryptInput != null) { - log.add(LogType.MSG_DATA_DECRYPT, 1); + log.add(LogType.MSG_DATA_OPENPGP, 1); PgpDecryptVerifyOperation op = new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable); @@ -175,7 +175,7 @@ public class InputDataOperation extends BaseOperation { out.write(buf, 0, len); } while ((len = is.read(buf)) > 0); - log.add(LogType.MSG_DATA_MIME_LENGTH, 3, totalLength); + log.add(LogType.MSG_DATA_MIME_LENGTH, 3, Long.toString(totalLength)); String charset = bd.getCharset(); // the charset defaults to us-ascii, but we want to default to utf-8 @@ -199,27 +199,26 @@ public class InputDataOperation extends BaseOperation { parser.parse(in); // if no mime data parsed, just return the raw data as fallback - if (outputUris.isEmpty()) { - - log.add(LogType.MSG_DATA_MIME_NONE, 2); - - OpenPgpMetadata metadata; - if (decryptResult != null) { - metadata = decryptResult.getDecryptionMetadata(); - } else { - // if we neither decrypted nor mime-decoded, should this be treated as an error? - // either way, we know nothing about the data - metadata = new OpenPgpMetadata(); - } - - outputUris.add(currentInputUri); - metadatas.add(metadata); + if (!outputUris.isEmpty()) { + log.add(LogType.MSG_DATA_MIME_OK, 2); log.add(LogType.MSG_DATA_OK, 1); return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, outputUris, metadatas); } - log.add(LogType.MSG_DATA_MIME_OK, 2); + log.add(LogType.MSG_DATA_MIME_NONE, 2); + + OpenPgpMetadata metadata; + if (decryptResult != null) { + metadata = decryptResult.getDecryptionMetadata(); + } else { + // if we neither decrypted nor mime-decoded, should this be treated as an error? + // either way, we know nothing about the data + metadata = new OpenPgpMetadata(); + } + + outputUris.add(currentInputUri); + metadatas.add(metadata); log.add(LogType.MSG_DATA_OK, 1); return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, outputUris, metadatas); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 70ff7b338..6db2539d5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -827,7 +827,7 @@ public abstract class OperationResult implements Parcelable { // InputData Operation MSG_DATA (LogLevel.START, R.string.msg_data), - MSG_DATA_DECRYPT (LogLevel.DEBUG, R.string.msg_data_decrypt), + MSG_DATA_OPENPGP (LogLevel.DEBUG, R.string.msg_data_openpgp), MSG_DATA_ERROR_IO (LogLevel.ERROR, R.string.msg_data_error_io), MSG_DATA_MIME_ERROR (LogLevel.ERROR, R.string.msg_data_mime_error), MSG_DATA_MIME_FILENAME (LogLevel.DEBUG, R.string.msg_data_mime_filename), diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index eafd0b879..c77147395 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1355,19 +1355,19 @@ "Format error!" "Resource not found!" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" + "Processing input data" + "Attempting to process OpenPGP data" + "Error reading input data!" + "Error parsing MIME data!" + "Filename: '%s'" + "Content-Length: %s" + "Parsing MIME data structure" + "Finished parsing + "No MIME structure found" + "Processing MIME part" + "Content-Type: %s" + "Data processing successful" + "Skipping MIME parsing" "Account saved" From 76465fc687f8dc1cfe9011f683fbe24ddc92005f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 20:48:46 +0200 Subject: [PATCH 22/34] mime: add failure case if openpgp processing fails! --- .../keychain/operations/InputDataOperation.java | 5 +++++ .../keychain/operations/results/OperationResult.java | 1 + OpenKeychain/src/main/res/values/strings.xml | 1 + 3 files changed, 7 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index 99bd0ebef..b95a42b82 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -91,6 +91,11 @@ public class InputDataOperation extends BaseOperation { } log.addByMerge(decryptResult, 2); + if (!decryptResult.success()) { + log.add(LogType.MSG_DATA_ERROR_OPENPGP, 1); + return new InputDataResult(InputDataResult.RESULT_ERROR, log); + } + } else { currentInputUri = input.getInputUri(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 6db2539d5..590a93872 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -829,6 +829,7 @@ public abstract class OperationResult implements Parcelable { MSG_DATA (LogLevel.START, R.string.msg_data), MSG_DATA_OPENPGP (LogLevel.DEBUG, R.string.msg_data_openpgp), MSG_DATA_ERROR_IO (LogLevel.ERROR, R.string.msg_data_error_io), + MSG_DATA_ERROR_OPENPGP (LogLevel.ERROR, R.string.msg_data_error_openpgp), MSG_DATA_MIME_ERROR (LogLevel.ERROR, R.string.msg_data_mime_error), MSG_DATA_MIME_FILENAME (LogLevel.DEBUG, R.string.msg_data_mime_filename), MSG_DATA_MIME_LENGTH (LogLevel.DEBUG, R.string.msg_data_mime_length), diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index c77147395..3dcb24fef 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1358,6 +1358,7 @@ "Processing input data" "Attempting to process OpenPGP data" "Error reading input data!" + "Error processing OpenPGP data!" "Error parsing MIME data!" "Filename: '%s'" "Content-Length: %s" From 5e0c40346eff669370be2bb0358dd878059e7092 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 20:57:46 +0200 Subject: [PATCH 23/34] multidecrypt: attempt file deletion only once at most --- .../sufficientlysecure/keychain/ui/DecryptListFragment.java | 3 +++ 1 file changed, 3 insertions(+) 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 5d6bc5e21..f630f6600 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -555,6 +555,9 @@ public class DecryptListFragment private void deleteFile(Activity activity, Uri uri) { + // we can only ever delete a file once, if we got this far either it's gone or it will never work + mCanDelete = false; + if ("file".equals(uri.getScheme())) { File file = new File(uri.getPath()); if (file.delete()) { From d5bde6997e6640f27d38f493e07a84e91a02fad6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 21:03:49 +0200 Subject: [PATCH 24/34] fail with warning if we get data from the AOSP mail client (see #290) --- .../keychain/ui/DecryptActivity.java | 12 +++++++++++- OpenKeychain/src/main/res/values/strings.xml | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 043929130..5eb9963f5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -159,7 +159,17 @@ public class DecryptActivity extends BaseActivity { canDelete = true; case OpenKeychainIntents.DECRYPT_DATA: default: - uris.add(intent.getData()); + Uri uri = intent.getData(); + if (uri != null) { + + if ("com.android.email.attachmentprovider".equals(uri.getHost())) { + Toast.makeText(this, R.string.error_reading_aosp, Toast.LENGTH_LONG).show(); + finish(); + return; + } + + uris.add(intent.getData()); + } } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 3dcb24fef..1f4fe0606 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1540,6 +1540,7 @@ "Error loading keys!" "(error, empty log)" "Could not read input to decrypt!" + "Failed reading data, this is a bug in the Android E-Mail client!" Unknown filename (click to open) Text (click to show) Show Signed/Encrypted Content From 1b8cdf5462890ebc10cabaa6955d5d05fea25e8f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 16 Sep 2015 22:58:23 +0200 Subject: [PATCH 25/34] update openpgp-api-lib with OpenPgpMetadata changes --- extern/openpgp-api-lib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/openpgp-api-lib b/extern/openpgp-api-lib index 13492ba19..0ba256969 160000 --- a/extern/openpgp-api-lib +++ b/extern/openpgp-api-lib @@ -1 +1 @@ -Subproject commit 13492ba19fcc1767f5589227b8fa0a9c845696d4 +Subproject commit 0ba25696981a4c4d5aef01e4a1d683c8adf7522a From f77a6a85ee70b0f1f1917855731d46551e3efcaf Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 17 Sep 2015 00:54:33 +0200 Subject: [PATCH 26/34] mime: don't decrypt in mime parsing only test --- .../keychain/pgp/InputDataOperationTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java index 59b52ea85..38af88a18 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/InputDataOperationTest.java @@ -127,8 +127,7 @@ public class InputDataOperationTest { InputDataOperation op = new InputDataOperation(spyApplication, new ProviderHelper(RuntimeEnvironment.application), null); - PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel(); - InputDataParcel input = new InputDataParcel(fakeInputUri, decryptInput); + InputDataParcel input = new InputDataParcel(fakeInputUri, null); InputDataResult result = op.execute(input, new CryptoInputParcel()); From acbb857f24c3283ae0e9a6b495e02825b40f5579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Thu, 17 Sep 2015 10:16:59 +0200 Subject: [PATCH 27/34] Add K-9 Mail warning toast --- .../org/sufficientlysecure/keychain/ui/DecryptActivity.java | 4 ++++ OpenKeychain/src/main/res/values/strings.xml | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 5eb9963f5..4b80af437 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -162,6 +162,10 @@ public class DecryptActivity extends BaseActivity { Uri uri = intent.getData(); if (uri != null) { + if ("com.fsck.k9.attachmentprovider".equals(uri.getHost())) { + Toast.makeText(this, R.string.error_reading_k9, Toast.LENGTH_LONG).show(); + } + if ("com.android.email.attachmentprovider".equals(uri.getHost())) { Toast.makeText(this, R.string.error_reading_aosp, Toast.LENGTH_LONG).show(); finish(); diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 1f4fe0606..2be695ffa 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1540,7 +1540,8 @@ "Error loading keys!" "(error, empty log)" "Could not read input to decrypt!" - "Failed reading data, this is a bug in the Android E-Mail client!" + "Failed reading data, this is a bug in the Android E-Mail client! (Issue #290)" + "If decryption fails, press 'Download complete message' in K-9 Mail!" Unknown filename (click to open) Text (click to show) Show Signed/Encrypted Content From 7da80c84b79f8b0760db7c42c77ab16f10131dda Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 17 Sep 2015 13:45:09 +0200 Subject: [PATCH 28/34] multidecrypt: use chooser intent on click for everything but text/plain ACTION_SHOW --- .../keychain/ui/DecryptListFragment.java | 47 +++++++++++++++---- OpenKeychain/src/main/res/values/strings.xml | 1 + 2 files changed, 38 insertions(+), 10 deletions(-) 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 f630f6600..ca89509c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -28,6 +28,7 @@ import android.app.Activity; import android.content.ClipDescription; import android.content.Context; import android.content.Intent; +import android.content.pm.LabeledIntent; import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.graphics.Point; @@ -36,6 +37,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.os.Parcelable; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -59,6 +61,7 @@ import android.widget.ViewAnimator; import com.cocosw.bottomsheet.BottomSheet; import org.openintents.openpgp.OpenPgpMetadata; import org.openintents.openpgp.OpenPgpSignatureResult; +import org.sufficientlysecure.keychain.BuildConfig; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.InputDataResult; @@ -430,10 +433,10 @@ public class DecryptListFragment public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.decrypt_open: - displayWithViewIntent(result, index, false); + displayWithViewIntent(result, index, false, true); break; case R.id.decrypt_share: - displayWithViewIntent(result, index, true); + displayWithViewIntent(result, index, true, true); break; case R.id.decrypt_save: saveFileDialog(result, index); @@ -445,7 +448,7 @@ public class DecryptListFragment } - public void displayWithViewIntent(InputDataResult result, int index, boolean share) { + public void displayWithViewIntent(InputDataResult result, int index, boolean share, boolean forceChooser) { Activity activity = getActivity(); if (activity == null) { return; @@ -466,7 +469,9 @@ public class DecryptListFragment Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, plaintext); - startActivity(intent); + + Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_share)); + startActivity(chooserIntent); } catch (IOException e) { Notify.create(activity, R.string.error_preparing_data, Style.ERROR).show(); @@ -475,12 +480,34 @@ public class DecryptListFragment return; } - Intent intent = new Intent(activity, DisplayTextActivity.class); + Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(outputUri, "text/plain"); - intent.putExtra(DisplayTextActivity.EXTRA_RESULT, result.mDecryptVerifyResult); - intent.putExtra(DisplayTextActivity.EXTRA_METADATA, metadata); - activity.startActivity(intent); + + if (forceChooser) { + + LabeledIntent internalIntent = new LabeledIntent( + new Intent(intent) + .setClass(activity, DisplayTextActivity.class) + .putExtra(DisplayTextActivity.EXTRA_RESULT, result.mDecryptVerifyResult) + .putExtra(DisplayTextActivity.EXTRA_METADATA, metadata), + BuildConfig.APPLICATION_ID, R.string.view_internal, R.mipmap.ic_launcher); + + Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, + new Parcelable[] { internalIntent }); + + startActivity(chooserIntent); + + } else { + + intent.setClass(activity, DisplayTextActivity.class); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.putExtra(DisplayTextActivity.EXTRA_RESULT, result.mDecryptVerifyResult); + intent.putExtra(DisplayTextActivity.EXTRA_METADATA, metadata); + startActivity(intent); + + } } else { @@ -498,7 +525,7 @@ public class DecryptListFragment Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); chooserIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - activity.startActivity(chooserIntent); + startActivity(chooserIntent); } } @@ -768,7 +795,7 @@ public class DecryptListFragment @Override public void onClick(View view) { if (model.mResult.success()) { - displayWithViewIntent(model.mResult, idx, false); + displayWithViewIntent(model.mResult, idx, false, false); } } }); diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 1f4fe0606..d0939026c 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1544,6 +1544,7 @@ Unknown filename (click to open) Text (click to show) Show Signed/Encrypted Content + Share Signed/Encrypted Content "View in OpenKeychain" "Error preparing data!" "Encrypted Data" From bbbc8dfd86a028aafeaa080be0ff1ef3da35b064 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 17 Sep 2015 16:17:17 +0200 Subject: [PATCH 29/34] multidecrypt: black share icon in bottom sheet --- Graphics/get-material-icons.sh | 1 + .../main/res/drawable-hdpi/ic_save_black_24dp.png | Bin 0 -> 240 bytes .../main/res/drawable-mdpi/ic_save_black_24dp.png | Bin 0 -> 167 bytes .../res/drawable-xhdpi/ic_save_black_24dp.png | Bin 0 -> 264 bytes .../res/drawable-xxhdpi/ic_save_black_24dp.png | Bin 0 -> 368 bytes .../res/drawable-xxxhdpi/ic_save_black_24dp.png | Bin 0 -> 477 bytes .../src/main/res/menu/decrypt_bottom_sheet.xml | 2 +- 7 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 OpenKeychain/src/main/res/drawable-hdpi/ic_save_black_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-mdpi/ic_save_black_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xhdpi/ic_save_black_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxhdpi/ic_save_black_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxxhdpi/ic_save_black_24dp.png diff --git a/Graphics/get-material-icons.sh b/Graphics/get-material-icons.sh index d4d72e5b3..11012b968 100755 --- a/Graphics/get-material-icons.sh +++ b/Graphics/get-material-icons.sh @@ -27,6 +27,7 @@ python copy OpenKeychain communication grey import_export 24 python copy OpenKeychain content grey content_copy 24 python copy OpenKeychain content grey content_paste 24 python copy OpenKeychain content grey save 24 +python copy OpenKeychain content black save 24 python copy OpenKeychain content grey select_all 24 python copy OpenKeychain editor grey mode_edit 24 python copy OpenKeychain file grey cloud 24 diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_save_black_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_save_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b959dc4a8ed3d974d59ec0d471506cd61998592e GIT binary patch literal 240 zcmVDVd5(>BJ=niteU%2YFH`2sfB`Tj|9A4842*mXP-3vb zsu0uSBC~9)6z59Hybe0-MqLbL#b@u0ptMkcQbLaETaY~#3cp$;6z*8)gnH_w;WOw# z%6l2J9!gm+*FwFx8Ff%CZkiuMgSeTrKRaW+KmDuCzopr096P-!~g&Q literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_save_black_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_save_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..eca2d92eccc5665b070d3c04e7241fbc2cbb71d5 GIT binary patch literal 264 zcmV+j0r&oiP)^5To~qfUqA&PMF*F<|kx;Md$&W&;x!#4;TRufSvnyKmY=;&`=O< zD`{EPz!xd^WlTz7A;m7|q5~RSbvmHHRiy(WT*Yq$l(?#OK#Qwh2dre+9fV)gZ4fyZ>#A&pWX|cF@nuJ>{9{^+CL!nen`MaoS0vDoPmT;NZb{N*U=t-36Ko{ O0000^c^fo|P|pSPy$;Uv zZr}&^4i28@d0tw9F;9FD|7rIeRgE{IJrya~94%=~w5KF3iRVT7B%T*3{@O9wBt6I` z=|MJ053)&mkWJEqY*KzubdZA_bmHX)Imkf{a*%@@v}Hn%Iu!~U^qJ-bt!VSN4rMOT z1DEWo$|477wTJUVD%GGZZ4T8{gC>V~KWl~mc+6>B{xvY@($^O)=WDCzEataaXtamIiqfk2BP^HUsXUqsP1 zB=sT#ElKK51{#sn1uF^Xm4d`-ybCzjkY3=HXErDL%1cHRc%J8ZDSZQI<(51+Brs$E O0000lItxES#dJw6yYM$|I#OM=zHzyti}Pwc^wN zt))Mh%cSoRUpghoOS5!|qNV?v4aVoI&jobrM2 z$mu=)$jDq&z1ekBu@?lqI?!8Eh|s}IjDKfvy^)|NSsGyL&A?F9>b zr|#PGi^XTftux9GW?F@2e|W@S;OgUD-ymq%`c2|5CSP&+_V&O1U75EBlQW zA57$ZmUC%mSY_hL8FrGAwa3xp#U)%C$H2?HsTP{@eO+0R%w9&s6jPrjS{Klkz&V60~+kL@gb&1nDt literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/menu/decrypt_bottom_sheet.xml b/OpenKeychain/src/main/res/menu/decrypt_bottom_sheet.xml index e38acd591..11b79bd5f 100644 --- a/OpenKeychain/src/main/res/menu/decrypt_bottom_sheet.xml +++ b/OpenKeychain/src/main/res/menu/decrypt_bottom_sheet.xml @@ -14,6 +14,6 @@ + android:icon="@drawable/ic_save_black_24dp" /> From ff2c552aa2316180e9f646cc4908391f51fc81f6 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 17 Sep 2015 16:17:32 +0200 Subject: [PATCH 30/34] white share icon in log display activity --- Graphics/get-material-icons.sh | 1 + .../res/drawable-hdpi/ic_share_white_24dp.png | Bin 0 -> 397 bytes .../res/drawable-mdpi/ic_share_white_24dp.png | Bin 0 -> 268 bytes .../res/drawable-xhdpi/ic_share_white_24dp.png | Bin 0 -> 496 bytes .../res/drawable-xxhdpi/ic_share_white_24dp.png | Bin 0 -> 698 bytes .../res/drawable-xxxhdpi/ic_share_white_24dp.png | Bin 0 -> 938 bytes OpenKeychain/src/main/res/menu/log_display.xml | 2 +- 7 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 OpenKeychain/src/main/res/drawable-hdpi/ic_share_white_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-mdpi/ic_share_white_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xhdpi/ic_share_white_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png diff --git a/Graphics/get-material-icons.sh b/Graphics/get-material-icons.sh index 11012b968..287e8ed82 100755 --- a/Graphics/get-material-icons.sh +++ b/Graphics/get-material-icons.sh @@ -38,6 +38,7 @@ python copy OpenKeychain navigation grey close 24 python copy OpenKeychain social grey person 24 python copy OpenKeychain social grey person_add 24 python copy OpenKeychain social grey share 24 +python copy OpenKeychain social white share 24 python copy OpenKeychain communication grey vpn_key 24 python copy OpenKeychain navigation grey chevron_left 24 python copy OpenKeychain navigation grey chevron_right 24 diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_share_white_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_share_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b09a6926de5aa48dee59265aadac32da236f9e1c GIT binary patch literal 397 zcmV;80doF{P)>pxai;{B2*!N!D>LzNpR^R2)fik{R2|aH0bP5 z*MdarD7a}-Ez;H;8^R0acFB7?>Nyer70h^N-MY7?BcJR3C~UbFc^uKRQ`h0hp&@lM za=0&bFUnK#Sn5WUrS5~&57j8c2`}{x^lU0@xf-K$z+Gzw`kpzeuI-nfmQ2OyT(N5C zy>l@-b#t1gVw*GChQ69riBYL)DYQJYY2deq4n`STc6((!%2Aj4=wg&-PHHZ4XiD9R z9L`I1T#6jpc6)6i%2KtYuxQ?fferHxMOo@@yJIr8nefKYHw~583zvN|v}`i=!hTIX z1AQ+XH*Uda9ScrJS&n(GZ(vPNp{o|$QBEc|F(1{8;NsTa>qG@_iujyHwlsAj>Z++)F8i@ z;dr8?qTnEcf#YSTw1b150_0@p@+*a+28_exGc&aM`@C!DnA6=W1TD^dHLWnpGa); zm^Y+_9MMXOw6r)E)Qv=1Dpnk1InpvI&qSnTPM&I{WK*8?NJ&ecW~5|Co@S)v zo;+)jihdt!$TJgZIOCp9j7RYtcTHy&qi7EMT1#hYjzy7Fyz4JnxBa7?nwO$TPFj`q zr?*r*?-yB%jzw_{yK2{dO_w|wdyK1E*V43R#&{IRA)lzr`oVKi2CrL}b=NCV1}DtR zdf-F7Q4|%E=4@)&v1!&s>@nzymi_)U?dd3vQ7f{R9MxmWmaIiDM3IbXXy<|7Wc}gI zD3(zTooV{g6HzP`D?0PNXQFr}b>fbfBMo!%)O?^fQn4w|-AGAGo@S(ETb^d5WJ8`i zk&;ORsJsW`0@zk4-OaZ6`@bTm>hqOLRBE*psAIi;?hzpcu;@0>~$&4^`LKYLci zIrnAV_F@#zgsOE-E$e2C#U2B`(z4&Ks^KV)cEMwO`8>gR#dEQ>t#buIhb5Q9LKD%GxreN1uxt9@X%L{wSIr@7a>|t0nC$dn$@& z&^LB;W=UV9V8l;4^I4?g4IA>+3`Qyrxu7A>*+|KZJl{o1ZpgC`DY-7se57PXp6N)* zlss=oN`^EXOV%)fi;J>`G%jw)8gjVE${JR2aaY!G0~hnM zhG|@UElYTe3EXtc3bxQs9yfX7vVdw{;WqBh$^xF`2P*M9I}|qJ3{e@6PKArjoMZ`q zzv59!fmanSI=PO&6?&+plSP6ovR7f^QNATeik(_R0|VqKmg6Jp6&~t1O&))jcuMQg zz!ARWI(NwM6X$u29SRTC9OM@M7CFo&S;iB5kG}$E*e=U>kWUFR#U6!^Dg|IO$GL;Q zdG;$z?BZ=E`Hcb|x0vJv&02@&xrVwOhY7LL@4M9>gD@+_--REpnJm3KuU?nF43nuCUR}5|x-@k1XJG+|9FJHqb%=H!GZ? zR#tEVHz}HB3FEjJmo=nuF(+%t;UXt%$l)REk1CR+@t5HZYGfZVddcE%mgiK277p?u!wk{S zAzEYsZHy7*0=xAuo@I(+(gP_7Qy#3 From 82ee9d440b804030682f28669d08650b6ade6ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Mon, 31 Aug 2015 20:52:00 +0200 Subject: [PATCH 31/34] Use material overflow icon in decrypt list Conflicts: OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseNfcActivity.java OpenKeychain/src/main/res/layout/decrypt_list_entry.xml --- Graphics/get-material-icons.sh | 1 + .../ic_menu_moreoverflow_normal_holo_light.png | Bin 148 -> 0 bytes .../res/drawable-hdpi/ic_more_vert_black_24dp.png | Bin 0 -> 132 bytes .../ic_menu_moreoverflow_normal_holo_light.png | Bin 131 -> 0 bytes .../res/drawable-mdpi/ic_more_vert_black_24dp.png | Bin 0 -> 108 bytes .../ic_menu_moreoverflow_normal_holo_light.png | Bin 184 -> 0 bytes .../drawable-xhdpi/ic_more_vert_black_24dp.png | Bin 0 -> 155 bytes .../drawable-xxhdpi/ic_more_vert_black_24dp.png | Bin 0 -> 205 bytes .../drawable-xxxhdpi/ic_more_vert_black_24dp.png | Bin 0 -> 272 bytes .../src/main/res/layout/decrypt_list_entry.xml | 7 ++++--- 10 files changed, 5 insertions(+), 3 deletions(-) delete mode 100644 OpenKeychain/src/main/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_light.png create mode 100644 OpenKeychain/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png delete mode 100644 OpenKeychain/src/main/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_light.png create mode 100644 OpenKeychain/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png delete mode 100644 OpenKeychain/src/main/res/drawable-xhdpi/ic_menu_moreoverflow_normal_holo_light.png create mode 100644 OpenKeychain/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxhdpi/ic_more_vert_black_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png diff --git a/Graphics/get-material-icons.sh b/Graphics/get-material-icons.sh index 287e8ed82..b6a2515c9 100755 --- a/Graphics/get-material-icons.sh +++ b/Graphics/get-material-icons.sh @@ -47,6 +47,7 @@ python copy OpenKeychain communication grey email 24 python copy OpenKeychain social black share 24 python copy OpenKeychain content black content_copy 24 python copy OpenKeychain communication black chat 24 +python copy OpenKeychain navigation black more_vert 24 # navigation drawer sections python copy OpenKeychain communication black vpn_key 24 diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_light.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_menu_moreoverflow_normal_holo_light.png deleted file mode 100644 index bb6aef1d069a14a7fc1cea9780c919c61679e4fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUtXipc%kc@k8uWjUIP~c#3xZ62X z=G4p^oaPsfxfS0Jish)R3f(&WG9yqa14Evg(y_dGGlfCy2iXqR3?>_SEs{VyhV?DH iJ0OaDc|iOR772{!kNsXaOPT8&i0|p@=d#Wzp$Py$I4IEo diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..22acc550088d98f9d98ced517de75b5e8ead3c7c GIT binary patch literal 132 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B5}8r;B4q1>@UGdwClSM3^70uIQ21 z47_&cJkx~*+Z%R2IKOxGVZM^fH+7^Mmwc{S?NKJ@tD@b{HffqWt7pJW9xm}`3bIR@ eOD6DrGLJi>8B(9~m^Tk-EQ6=3pUXO@geCy;;w$k0 literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_light.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_light.png deleted file mode 100644 index 01d681697f799729aa60bcc03878b8718ef5f705..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzFHaZ8kch)?uW#gKP~c&9OkaNH zNWhsXa}P0HP*CC8e>x;QVw%w1wevX{81fiqT9$oZHH(4a215zM2{s3I28IOo7eJ#J d>KPlRN5&o6d@ywr|9haR44$rjF6*2UngC}HDAND{ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..0e4f2f6ea0564fe9d6ebdb4bea0df48aced9de0f GIT binary patch literal 108 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1V^0^ykP60RiERoU4^0niv(+!U z$~^Ty`=RMavMX4xeQ2uS{l}x(rHwor_8Hg9|Y9Gj8!^hz0Vk4~QZ}ABcvv nF)(akj$uTI?LshtEWgbR4Rf6*RPme*0Ev6L`njxgN@xNAEX^ml diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..9f10aa2759a2e53beb129849c9b0e6ee5f7ae97b GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0DBu^K|kP61PR}A@_6b0BGTJ|c& z&2zArUdgs%qoJD@@9neO|NgtXV7^DW)MQ81qkHN%-79zXF}ZeKv~Tuphvz#4Z3DJ9 zo+<2n{rTU)UWQJm<&*zc>PM$^=S>$DV@#Ph3 zmm-(IJxlAmHdKggydBZHA?LrN(f;zraK2yFla}<&ocsFTF&FO*>g+$WJLfsbvhR#o zqU5?6`INL2k&sND<*?!p=%CFlpb5%Rrp)D#!8^w7WtAYxjJ8qaZaq8!Lha3*C xpO9i0)g!jJUB|8aV$u`{=V%64OvRJ&URzP%pP$_?yn*gx@O1TaS?83{1OO~yQ~m$| literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_more_vert_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4642a3b66e690a25544c7a65b5484607c76e9e4d GIT binary patch literal 272 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgu6w#ThEy=Vz2TVE>>%O(aeKF| z&%}AAoaYX07I~@2Qzf){=E~b*b6q_X?JD-aXZn{o>8SH070*d=@0yRCD&F7PI4|Iy zgr>dT{)s<*uuMbP?bJzL!+Uj>du1MOpP(^y_vJlXtsXdjo_CdTHuJx`L9zyy z_bjw};Pttx=xP2JS*8CI?{O?lw)*qOUytSD#5&t0XL`98ez{Y*;jMY`-`}^VtDMwJ z5#Pjpv-OQrnIiiQFtvL^tj8o3AbA2TH#1Zw0j|Uo=)7NS4apHKER!-ifL>(qboFyt I=akR{05Df}#Q*>R literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml index 577d825b7..7869b9a8a 100644 --- a/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml +++ b/OpenKeychain/src/main/res/layout/decrypt_list_entry.xml @@ -90,11 +90,12 @@ + android:background="?attr/selectableItemBackgroundBorderless" + android:src="@drawable/ic_more_vert_black_24dp" /> From fdb1a7384e81d6b395ec6d930a1df46eb7db9496 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 17 Sep 2015 19:48:51 +0200 Subject: [PATCH 32/34] multidcrypt: display k9 download only after decryption failure --- .../org/sufficientlysecure/keychain/ui/DecryptActivity.java | 4 ---- .../sufficientlysecure/keychain/ui/DecryptListFragment.java | 6 ++++++ OpenKeychain/src/main/res/values/strings.xml | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java index 4b80af437..5eb9963f5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptActivity.java @@ -162,10 +162,6 @@ public class DecryptActivity extends BaseActivity { Uri uri = intent.getData(); if (uri != null) { - if ("com.fsck.k9.attachmentprovider".equals(uri.getHost())) { - Toast.makeText(this, R.string.error_reading_k9, Toast.LENGTH_LONG).show(); - } - if ("com.android.email.attachmentprovider".equals(uri.getHost())) { Toast.makeText(this, R.string.error_reading_aosp, Toast.LENGTH_LONG).show(); finish(); 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 ca89509c5..dcba595e9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -56,6 +56,7 @@ import android.widget.PopupMenu.OnDismissListener; import android.widget.PopupMenu.OnMenuItemClickListener; import android.widget.ProgressBar; import android.widget.TextView; +import android.widget.Toast; import android.widget.ViewAnimator; import com.cocosw.bottomsheet.BottomSheet; @@ -304,6 +305,11 @@ public class DecryptListFragment final Uri uri = mCurrentInputUri; mCurrentInputUri = null; + Activity activity = getActivity(); + if (activity != null && "com.fsck.k9.attachmentprovider".equals(uri.getHost())) { + Toast.makeText(getActivity(), R.string.error_reading_k9, Toast.LENGTH_LONG).show(); + } + mAdapter.addResult(uri, result); cryptoOperation(); diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 0db982a8b..6b88350e6 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1541,7 +1541,7 @@ "(error, empty log)" "Could not read input to decrypt!" "Failed reading data, this is a bug in the Android E-Mail client! (Issue #290)" - "If decryption fails, press 'Download complete message' in K-9 Mail!" + "Received incomplete data, try pressing 'Download complete message' in K-9 Mail!" Unknown filename (click to open) Text (click to show) Show Signed/Encrypted Content From 955a1f4b2664dd548a71a343a4792bf1a4c8cfa9 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 17 Sep 2015 21:51:11 +0200 Subject: [PATCH 33/34] mime: support for signed-then-encrypted format --- .../operations/InputDataOperation.java | 148 ++++++++++++++++-- .../operations/results/OperationResult.java | 8 +- .../pgp/PgpDecryptVerifyInputParcel.java | 1 + OpenKeychain/src/main/res/values/strings.xml | 6 + 4 files changed, 147 insertions(+), 16 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index b95a42b82..56e7d822d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.operations; +import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -51,7 +52,16 @@ import org.sufficientlysecure.keychain.service.InputDataParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; -/** This operation deals with input data, trying to determine its type as it goes. */ +/** This operation deals with input data, trying to determine its type as it goes. + * + * We deal with four types of structures: + * + * - signed/encrypted non-mime data + * - signed/encrypted mime data + * - encrypted multipart/signed mime data + * - multipart/signed mime data (WIP) + * + */ public class InputDataOperation extends BaseOperation { final private byte[] buf = new byte[256]; @@ -60,9 +70,12 @@ public class InputDataOperation extends BaseOperation { super(context, providerHelper, progressable); } + Uri mSignedDataUri; + DecryptVerifyResult mSignedDataResult; + @NonNull @Override - public InputDataResult execute(InputDataParcel input, CryptoInputParcel cryptoInput) { + public InputDataResult execute(InputDataParcel input, final CryptoInputParcel cryptoInput) { final OperationLog log = new OperationLog(); @@ -119,16 +132,7 @@ public class InputDataOperation extends BaseOperation { } - log.add(LogType.MSG_DATA_MIME, 1); - - InputStream in; - try { - in = mContext.getContentResolver().openInputStream(currentInputUri); - } catch (FileNotFoundException e) { - log.add(LogType.MSG_DATA_ERROR_IO, 2); - return new InputDataResult(InputDataResult.RESULT_ERROR, log); - } - MimeStreamParser parser = new MimeStreamParser((MimeConfig) null); + final MimeStreamParser parser = new MimeStreamParser((MimeConfig) null); final ArrayList outputUris = new ArrayList<>(); final ArrayList metadatas = new ArrayList<>(); @@ -136,8 +140,55 @@ public class InputDataOperation extends BaseOperation { parser.setContentDecoding(true); parser.setRecurse(); parser.setContentHandler(new AbstractContentHandler() { + private Uri uncheckedSignedDataUri; String mFilename; + @Override + public void startMultipart(BodyDescriptor bd) throws MimeException { + if ("signed".equals(bd.getSubType())) { + if (mSignedDataResult != null) { + // recursive signed data is not supported! + log.add(LogType.MSG_DATA_DETACHED_NESTED, 2); + return; + } + log.add(LogType.MSG_DATA_DETACHED, 2); + if (!outputUris.isEmpty()) { + // we can't have previous data if we parse a detached signature! + log.add(LogType.MSG_DATA_DETACHED_CLEAR, 3); + outputUris.clear(); + metadatas.clear(); + } + // this is signed data, we require the next part raw + parser.setRaw(); + } + } + + @Override + public void raw(InputStream is) throws MimeException, IOException { + + if (uncheckedSignedDataUri != null) { + throw new AssertionError("raw parts must only be received as first part of multipart/signed!"); + } + + log.add(LogType.MSG_DATA_DETACHED_RAW, 3); + + uncheckedSignedDataUri = TemporaryStorageProvider.createFile(mContext, mFilename, "text/plain"); + OutputStream out = mContext.getContentResolver().openOutputStream(uncheckedSignedDataUri, "w"); + + if (out == null) { + throw new IOException("Error getting file for writing!"); + } + + int len; + while ((len = is.read(buf)) > 0) { + out.write(buf, 0, len); + } + + out.close(); + parser.setFlat(); + + } + @Override public void startHeader() throws MimeException { mFilename = null; @@ -151,9 +202,57 @@ public class InputDataOperation extends BaseOperation { } } + private void bodySignature(BodyDescriptor bd, InputStream is) throws MimeException, IOException { + + if (!"application/pgp-signature".equals(bd.getMimeType())) { + log.add(LogType.MSG_DATA_DETACHED_UNSUPPORTED, 3); + uncheckedSignedDataUri = null; + parser.setRecurse(); + return; + } + + log.add(LogType.MSG_DATA_DETACHED_SIG, 3); + + ByteArrayOutputStream detachedSig = new ByteArrayOutputStream(); + + int len, totalLength = 0; + while ((len = is.read(buf)) > 0) { + totalLength += len; + detachedSig.write(buf, 0, len); + if (totalLength > 4096) { + throw new IOException("detached signature is unreasonably large!"); + } + } + detachedSig.close(); + + PgpDecryptVerifyInputParcel decryptInput = new PgpDecryptVerifyInputParcel(); + decryptInput.setInputUri(uncheckedSignedDataUri); + decryptInput.setDetachedSignature(detachedSig.toByteArray()); + + PgpDecryptVerifyOperation op = + new PgpDecryptVerifyOperation(mContext, mProviderHelper, mProgressable); + DecryptVerifyResult verifyResult = op.execute(decryptInput, cryptoInput); + + log.addByMerge(verifyResult, 4); + + mSignedDataUri = uncheckedSignedDataUri; + mSignedDataResult = verifyResult; + + // reset parser state + uncheckedSignedDataUri = null; + parser.setRecurse(); + + } + @Override public void body(BodyDescriptor bd, InputStream is) throws MimeException, IOException { + // if we have signed data waiting, we expect a signature for checking + if (uncheckedSignedDataUri != null) { + bodySignature(bd, is); + return; + } + // we read first, no need to create an output file if nothing was read! int len = is.read(buf); if (len < 0) { @@ -196,14 +295,29 @@ public class InputDataOperation extends BaseOperation { } - }); try { + log.add(LogType.MSG_DATA_MIME, 1); + + // open current uri for input + InputStream in = mContext.getContentResolver().openInputStream(currentInputUri); parser.parse(in); - // if no mime data parsed, just return the raw data as fallback + if (mSignedDataUri != null) { + + if (decryptResult != null) { + decryptResult.setSignatureResult(mSignedDataResult.getSignatureResult()); + } else { + decryptResult = mSignedDataResult; + } + + in = mContext.getContentResolver().openInputStream(mSignedDataUri); + parser.parse(in); + } + + // if we found data, return success if (!outputUris.isEmpty()) { log.add(LogType.MSG_DATA_MIME_OK, 2); @@ -211,6 +325,7 @@ public class InputDataOperation extends BaseOperation { return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, outputUris, metadatas); } + // if no mime data parsed, just return the raw data as fallback log.add(LogType.MSG_DATA_MIME_NONE, 2); OpenPgpMetadata metadata; @@ -228,9 +343,12 @@ public class InputDataOperation extends BaseOperation { log.add(LogType.MSG_DATA_OK, 1); return new InputDataResult(InputDataResult.RESULT_OK, log, decryptResult, outputUris, metadatas); + } catch (FileNotFoundException e) { + log.add(LogType.MSG_DATA_ERROR_IO, 2); + return new InputDataResult(InputDataResult.RESULT_ERROR, log); } catch (IOException e) { e.printStackTrace(); - log.add(LogType.MSG_DATA_MIME_ERROR, 2); + log.add(LogType.MSG_DATA_ERROR_IO, 2); return new InputDataResult(InputDataResult.RESULT_ERROR, log); } catch (MimeException e) { e.printStackTrace(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 590a93872..3fb5be0ad 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -830,6 +830,12 @@ public abstract class OperationResult implements Parcelable { MSG_DATA_OPENPGP (LogLevel.DEBUG, R.string.msg_data_openpgp), MSG_DATA_ERROR_IO (LogLevel.ERROR, R.string.msg_data_error_io), MSG_DATA_ERROR_OPENPGP (LogLevel.ERROR, R.string.msg_data_error_openpgp), + MSG_DATA_DETACHED (LogLevel.INFO, R.string.msg_data_detached), + MSG_DATA_DETACHED_CLEAR (LogLevel.WARN, R.string.msg_data_detached_clear), + MSG_DATA_DETACHED_SIG (LogLevel.DEBUG, R.string.msg_data_detached_sig), + MSG_DATA_DETACHED_RAW (LogLevel.DEBUG, R.string.msg_data_detached_raw), + MSG_DATA_DETACHED_NESTED(LogLevel.WARN, R.string.msg_data_detached_nested), + MSG_DATA_DETACHED_UNSUPPORTED (LogLevel.WARN, R.string.msg_data_detached_unsupported), MSG_DATA_MIME_ERROR (LogLevel.ERROR, R.string.msg_data_mime_error), MSG_DATA_MIME_FILENAME (LogLevel.DEBUG, R.string.msg_data_mime_filename), MSG_DATA_MIME_LENGTH (LogLevel.DEBUG, R.string.msg_data_mime_length), @@ -1004,7 +1010,7 @@ public abstract class OperationResult implements Parcelable { for (LogEntryParcel entry : this) { log.append(entry.getPrintableLogEntry(resources, indent)).append("\n"); } - return log.toString().substring(0, log.length() -1); // get rid of extra new line + return log.toString().substring(0, log.length() - 1); // get rid of extra new line } } 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 d56c24f91..3eef7759c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java @@ -18,6 +18,7 @@ package org.sufficientlysecure.keychain.pgp; +import java.io.InputStream; import java.util.HashSet; import android.net.Uri; diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 6b88350e6..5bb6761f5 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1357,6 +1357,12 @@ "Processing input data" "Attempting to process OpenPGP data" + "Encountered detached signature" + "Clearing earlier, unsigned data!" + "Processing detached signature" + "Processing signed data" + "Skipping nested signed data!" + "Unsupported type of detached signature!" "Error reading input data!" "Error processing OpenPGP data!" "Error parsing MIME data!" From 8ad31e32519b42c3ae439baa52716792980c5638 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Thu, 17 Sep 2015 22:10:37 +0200 Subject: [PATCH 34/34] mime: skip trailing unsigned parts, and ignore nested signed data --- .../operations/InputDataOperation.java | 19 ++++++++++++++++--- .../operations/results/OperationResult.java | 1 + OpenKeychain/src/main/res/values/strings.xml | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index 56e7d822d..d9e48af8a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -146,8 +146,8 @@ public class InputDataOperation extends BaseOperation { @Override public void startMultipart(BodyDescriptor bd) throws MimeException { if ("signed".equals(bd.getSubType())) { - if (mSignedDataResult != null) { - // recursive signed data is not supported! + if (mSignedDataUri != null) { + // recursive signed data is not supported, and will just be parsed as-is log.add(LogType.MSG_DATA_DETACHED_NESTED, 2); return; } @@ -185,6 +185,7 @@ public class InputDataOperation extends BaseOperation { } out.close(); + // continue to next body part the usual way parser.setFlat(); } @@ -259,6 +260,13 @@ public class InputDataOperation extends BaseOperation { return; } + // If mSignedDataUri is non-null, we already parsed a signature. If mSignedDataResult is non-null + // too, we are still in the same parsing stage, so this is trailing data - skip it! + if (mSignedDataUri != null && mSignedDataResult != null) { + log.add(LogType.MSG_DATA_DETACHED_TRAILING, 2); + return; + } + log.add(LogType.MSG_DATA_MIME_PART, 2); log.add(LogType.MSG_DATA_MIME_TYPE, 3, bd.getMimeType()); @@ -313,8 +321,13 @@ public class InputDataOperation extends BaseOperation { decryptResult = mSignedDataResult; } - in = mContext.getContentResolver().openInputStream(mSignedDataUri); + // the actual content is the signed data now (and will be passed verbatim, if parsing fails) + currentInputUri = mSignedDataUri; + in = mContext.getContentResolver().openInputStream(currentInputUri); + // reset signed data result, to indicate to the parser that it is in the inner part + mSignedDataResult = null; parser.parse(in); + } // if we found data, return success diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index 3fb5be0ad..b1dcc9202 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -835,6 +835,7 @@ public abstract class OperationResult implements Parcelable { MSG_DATA_DETACHED_SIG (LogLevel.DEBUG, R.string.msg_data_detached_sig), MSG_DATA_DETACHED_RAW (LogLevel.DEBUG, R.string.msg_data_detached_raw), MSG_DATA_DETACHED_NESTED(LogLevel.WARN, R.string.msg_data_detached_nested), + MSG_DATA_DETACHED_TRAILING (LogLevel.WARN, R.string.msg_data_detached_trailing), MSG_DATA_DETACHED_UNSUPPORTED (LogLevel.WARN, R.string.msg_data_detached_unsupported), MSG_DATA_MIME_ERROR (LogLevel.ERROR, R.string.msg_data_mime_error), MSG_DATA_MIME_FILENAME (LogLevel.DEBUG, R.string.msg_data_mime_filename), diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 5bb6761f5..f12be176e 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1362,6 +1362,7 @@ "Processing detached signature" "Processing signed data" "Skipping nested signed data!" + "Skipping trailing data after signed part!" "Unsupported type of detached signature!" "Error reading input data!" "Error processing OpenPGP data!"