diff options
author | Dominik Schürmann <dominik@dominikschuermann.de> | 2016-02-23 16:06:59 +0100 |
---|---|---|
committer | Dominik Schürmann <dominik@dominikschuermann.de> | 2016-02-23 16:06:59 +0100 |
commit | ff0edb2bfe3a729d1ede1f7f03a1ec592669169b (patch) | |
tree | f55a2f6bdf3e2625aa21bcb90801d7ba3b5df5b7 /OpenKeychain/src/main/java/org | |
parent | 73ce6fc7bb9cd5a024bfe510e862dd9d6af3d2de (diff) | |
parent | fd24acbf0e54be2be222ca107d97c63f308a1d4a (diff) | |
download | open-keychain-ff0edb2bfe3a729d1ede1f7f03a1ec592669169b.tar.gz open-keychain-ff0edb2bfe3a729d1ede1f7f03a1ec592669169b.tar.bz2 open-keychain-ff0edb2bfe3a729d1ede1f7f03a1ec592669169b.zip |
Merge branch 'master' of github.com:open-keychain/open-keychain
Diffstat (limited to 'OpenKeychain/src/main/java/org')
7 files changed, 229 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 bb8d6ad73..43fc11b84 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -53,6 +53,7 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; import org.sufficientlysecure.keychain.service.InputDataParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.util.CharsetVerifier; /** This operation deals with input data, trying to determine its type as it goes. @@ -67,7 +68,7 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; */ public class InputDataOperation extends BaseOperation<InputDataParcel> { - final private byte[] buf = new byte[256]; + private final byte[] buf = new byte[256]; public InputDataOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { super(context, providerHelper, progressable); @@ -326,21 +327,37 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> { throw new IOException("Error getting file for writing!"); } + // If this data looks like text, we pipe the incoming data into a charset + // decoder, to see if the data is legal for the assumed charset. + String charset = bd.getCharset(); + CharsetVerifier charsetVerifier = new CharsetVerifier(buf, mimeType, charset); + int totalLength = 0; do { totalLength += len; out.write(buf, 0, len); + charsetVerifier.readBytesFromBuffer(0, len); } while ((len = is.read(buf)) > 0); 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 - if ("us-ascii".equals(charset)) { - charset = "utf-8"; - } + OpenPgpMetadata metadata; + if (charsetVerifier.isDefinitelyBinary()) { + metadata = new OpenPgpMetadata(mFilename, mimeType, 0L, totalLength); + } else { + if (charsetVerifier.isCharsetFaulty() && charsetVerifier.isCharsetGuessed()) { + log.add(LogType.MSG_DATA_MIME_CHARSET_UNKNOWN, 3, charsetVerifier.getMaybeFaultyCharset()); + } else if (charsetVerifier.isCharsetFaulty()) { + log.add(LogType.MSG_DATA_MIME_CHARSET_FAULTY, 3, charsetVerifier.getCharset()); + } else if (charsetVerifier.isCharsetGuessed()) { + log.add(LogType.MSG_DATA_MIME_CHARSET_GUESS, 3, charsetVerifier.getCharset()); + } else { + log.add(LogType.MSG_DATA_MIME_CHARSET, 3, charsetVerifier.getCharset()); + } - OpenPgpMetadata metadata = new OpenPgpMetadata(mFilename, mimeType, 0L, totalLength, charset); + metadata = new OpenPgpMetadata(mFilename, charsetVerifier.getGuessedMimeType(), 0L, totalLength, + charsetVerifier.getCharset()); + } out.close(); outputUris.add(uri); 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 f9c2db8e8..ec2fddbd0 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 @@ -860,6 +860,10 @@ public abstract class OperationResult implements Parcelable { MSG_DATA_MIME_FROM_EXTENSION (LogLevel.DEBUG, R.string.msg_data_mime_from_extension), 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_CHARSET (LogLevel.DEBUG, R.string.msg_data_mime_charset), + MSG_DATA_MIME_CHARSET_FAULTY (LogLevel.WARN, R.string.msg_data_mime_charset_faulty), + MSG_DATA_MIME_CHARSET_GUESS (LogLevel.DEBUG, R.string.msg_data_mime_charset_guess), + MSG_DATA_MIME_CHARSET_UNKNOWN (LogLevel.DEBUG, R.string.msg_data_mime_charset_unknown), 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), 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 c4755c7c5..e15139a7f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -35,8 +35,6 @@ import android.support.annotation.NonNull; import android.text.TextUtils; import android.webkit.MimeTypeMap; -import org.openintents.openpgp.OpenPgpDecryptionResult; -import org.openintents.openpgp.OpenPgpMetadata; import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPDataValidationException; @@ -56,10 +54,13 @@ import org.bouncycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; import org.bouncycastle.util.encoders.DecoderException; +import org.openintents.openpgp.OpenPgpDecryptionResult; +import org.openintents.openpgp.OpenPgpMetadata; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants.key; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.BaseOperation; +import org.sufficientlysecure.keychain.util.CharsetVerifier; import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -387,9 +388,9 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp MimeTypeMap mime = MimeTypeMap.getSingleton(); mimeType = mime.getMimeTypeFromExtension(extension); } - if (mimeType == null) { - mimeType = "application/octet-stream"; - } + } + if (mimeType == null) { + mimeType = "application/octet-stream"; } if (!"".equals(originalFilename)) { @@ -414,11 +415,9 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp } metadata = new OpenPgpMetadata( - originalFilename, - mimeType, + originalFilename, mimeType, literalData.getModificationTime().getTime(), - originalSize == null ? 0 : originalSize, - charset); + originalSize == null ? 0 : originalSize, charset); log.add(LogType.MSG_DC_OK_META_ONLY, indent); DecryptVerifyResult result = @@ -439,6 +438,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp int length; byte[] buffer = new byte[8192]; byte[] firstBytes = new byte[48]; + CharsetVerifier charsetVerifier = new CharsetVerifier(buffer, mimeType, charset); + while ((length = dataIn.read(buffer)) > 0) { // Log.d(Constants.TAG, "read bytes: " + length); if (out != null) { @@ -448,6 +449,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp // update signature buffer if signature is also present signatureChecker.updateSignatureData(buffer, 0, length); + charsetVerifier.readBytesFromBuffer(0, length); + // note down first couple of bytes for "magic bytes" file type detection if (alreadyWritten == 0) { System.arraycopy(buffer, 0, firstBytes, 0, length > firstBytes.length ? firstBytes.length : length); @@ -480,18 +483,21 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp Log.d(Constants.TAG, "decrypt time taken: " + String.format("%.2f", opTime / 1000.0) + "s"); // special treatment to detect pgp mime types + // TODO move into CharsetVerifier? seems like that would be a plausible place for this logic if (matchesPrefix(firstBytes, "-----BEGIN PGP PUBLIC KEY BLOCK-----") || matchesPrefix(firstBytes, "-----BEGIN PGP PRIVATE KEY BLOCK-----")) { mimeType = Constants.MIME_TYPE_KEYS; } else if (matchesPrefix(firstBytes, "-----BEGIN PGP MESSAGE-----")) { // this is NOT application/pgp-encrypted, see RFC 3156! mimeType = Constants.MIME_TYPE_ENCRYPTED_ALTERNATE; + } else { + mimeType = charsetVerifier.getGuessedMimeType(); } + metadata = new OpenPgpMetadata(originalFilename, mimeType, literalData.getModificationTime().getTime(), + alreadyWritten, charsetVerifier.getCharset()); log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, mimeType); - - metadata = new OpenPgpMetadata( - originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten, charset); + Log.d(Constants.TAG, metadata.toString()); indent -= 1; @@ -873,11 +879,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp log.add(LogType.MSG_DC_OK, indent); - OpenPgpMetadata metadata = new OpenPgpMetadata( - "", - "text/plain", - -1, - clearText.length); + OpenPgpMetadata metadata = new OpenPgpMetadata("", "text/plain", -1, clearText.length, "utf-8"); DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); result.setSignatureResult(signatureChecker.getSignatureResult()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java index 351b62ba7..bc3470b0a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui; import java.util.ArrayList; +import android.app.Activity; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -472,6 +473,16 @@ public abstract class DecryptFragment extends Fragment implements LoaderManager. protected abstract void onVerifyLoaded(boolean hideErrorOverlay); + public void startDisplayLogActivity() { + Activity activity = getActivity(); + if (activity == null) { + return; + } + Intent intent = new Intent(activity, LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, mDecryptVerifyResult); + activity.startActivity(intent); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mImportOpHelper != null) { 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 9419cf8ce..6f85342d6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptListFragment.java @@ -591,6 +591,18 @@ public class DecryptListFragment Intent chooserIntent = Intent.createChooser(intent, getString(R.string.intent_show)); chooserIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + if (!share && ClipDescription.compareMimeTypes(metadata.getMimeType(), "text/*")) { + 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); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, + new Parcelable[] { internalIntent }); + } + startActivity(chooserIntent); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java index 1060714f0..97f723168 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DisplayTextFragment.java @@ -103,7 +103,7 @@ public class DisplayTextFragment extends DecryptFragment { Bundle args = getArguments(); String plaintext = args.getString(ARG_PLAINTEXT); - DecryptVerifyResult result = args.getParcelable(ARG_DECRYPT_VERIFY_RESULT); + DecryptVerifyResult result = args.getParcelable(ARG_DECRYPT_VERIFY_RESULT); // display signature result in activity mText.setText(plaintext); @@ -137,6 +137,10 @@ public class DisplayTextFragment extends DecryptFragment { copyToClipboard(mText.getText().toString()); break; } + case R.id.decrypt_view_log: { + startDisplayLogActivity(); + break; + } default: { return super.onOptionsItemSelected(item); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/CharsetVerifier.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/CharsetVerifier.java new file mode 100644 index 000000000..c03decc89 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/CharsetVerifier.java @@ -0,0 +1,154 @@ +package org.sufficientlysecure.keychain.util; + + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; + +import android.content.ClipDescription; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** This class can be used to guess whether a stream of data is encoded in a given + * charset or not. + * + * An object of this class must be initialized with a byte[] buffer, which should + * be filled with data, then processed with {@link #readBytesFromBuffer}. This can + * be done any number of times. Once all data has been read, a final status can be + * read using the getter methods. + */ +public class CharsetVerifier { + + private final ByteBuffer bufWrap; + private final CharBuffer dummyOutput; + + private final CharsetDecoder charsetDecoder; + + private boolean isFinished; + private boolean isFaulty; + private boolean isGuessed; + private boolean isPossibleTextMimeType; + private boolean isTextMimeType; + private String charset; + private String mimeType; + + public CharsetVerifier(@NonNull byte[] buf, @NonNull String mimeType, @Nullable String charset) { + + this.mimeType = mimeType; + isTextMimeType = ClipDescription.compareMimeTypes(mimeType, "text/*"); + isPossibleTextMimeType = isTextMimeType + || ClipDescription.compareMimeTypes(mimeType, "application/octet-stream") + || ClipDescription.compareMimeTypes(mimeType, "application/x-download"); + if (!isPossibleTextMimeType) { + charsetDecoder = null; + bufWrap = null; + dummyOutput = null; + return; + } + + bufWrap = ByteBuffer.wrap(buf); + dummyOutput = CharBuffer.allocate(buf.length); + + // the charset defaults to us-ascii, but we want to default to utf-8 + if (charset == null || "us-ascii".equals(charset)) { + charset = "utf-8"; + isGuessed = true; + } else { + isGuessed = false; + } + this.charset = charset; + + charsetDecoder = Charset.forName(charset).newDecoder(); + charsetDecoder.onMalformedInput(CodingErrorAction.REPORT); + charsetDecoder.onUnmappableCharacter(CodingErrorAction.REPORT); + charsetDecoder.reset(); + } + + public void readBytesFromBuffer(int pos, int len) { + if (isFinished) { + throw new IllegalStateException("cannot write again after reading charset status!"); + } + if (isFaulty || bufWrap == null) { + return; + } + bufWrap.rewind(); + bufWrap.position(pos); + bufWrap.limit(len); + dummyOutput.rewind(); + CoderResult result = charsetDecoder.decode(bufWrap, dummyOutput, false); + if (result.isError()) { + isFaulty = true; + } + } + + private void finishIfNecessary() { + if (isFinished || isFaulty || bufWrap == null) { + return; + } + isFinished = true; + bufWrap.rewind(); + bufWrap.limit(0); + dummyOutput.rewind(); + CoderResult result = charsetDecoder.decode(bufWrap, dummyOutput, true); + if (result.isError()) { + isFaulty = true; + } + } + + public String getGuessedMimeType() { + if (isTextMimeType) { + return mimeType; + } + if (isProbablyText()) { + return "text/plain"; + } + return mimeType; + } + + public boolean isCharsetFaulty() { + finishIfNecessary(); + return isFaulty; + } + + public boolean isCharsetGuessed() { + finishIfNecessary(); + return isGuessed; + } + + public String getCharset() { + finishIfNecessary(); + if (!isPossibleTextMimeType || (isGuessed && isFaulty)) { + return null; + } + return charset; + } + + public String getMaybeFaultyCharset() { + return charset; + } + + /** Returns true if the data which was read is definitely binary. + * + * This can happen when either the supplied mimeType indicated a non-ambiguous + * binary data type, or if we guessed a charset but got errors while decoding. + */ + public boolean isDefinitelyBinary() { + finishIfNecessary(); + return !isTextMimeType && (!isPossibleTextMimeType || (isGuessed && isFaulty)); + } + + /** Returns true iff the data which was read is probably (or + * definitely) text. + * + * The corner case where isDefinitelyBinary returns false but isProbablyText + * returns true is where the charset was provided by the data (so is not + * guessed) but is still faulty. + */ + public boolean isProbablyText() { + finishIfNecessary(); + return isTextMimeType || isPossibleTextMimeType && (!isGuessed || !isFaulty); + } +} |