From daf243082c6cd7fb7f518bfbf0acf9acafaa27d1 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 22 Feb 2016 21:12:36 +0100 Subject: externalize CharsetVerifier, add looksLikeText to OpenPgpMetadata object --- .../keychain/operations/CharsetVerifier.java | 122 +++++++++++++++++++++ .../keychain/operations/InputDataOperation.java | 94 +++------------- .../keychain/pgp/PgpDecryptVerifyOperation.java | 19 ++-- 3 files changed, 148 insertions(+), 87 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CharsetVerifier.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CharsetVerifier.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CharsetVerifier.java new file mode 100644 index 000000000..5d63ced22 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CharsetVerifier.java @@ -0,0 +1,122 @@ +package org.sufficientlysecure.keychain.operations; + + +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; + + +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; + + public CharsetVerifier(@NonNull byte[] buf, String mimeType, @Nullable String charset) { + + isPossibleTextMimeType = ClipDescription.compareMimeTypes(mimeType, "application/octet-stream") + || ClipDescription.compareMimeTypes(mimeType, "application/x-download") + || ClipDescription.compareMimeTypes(mimeType, "text/*"); + if (!isPossibleTextMimeType) { + charsetDecoder = null; + bufWrap = null; + dummyOutput = null; + return; + } + isTextMimeType = ClipDescription.compareMimeTypes(mimeType, "text/*"); + + 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 write(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 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; + } + + public boolean isDefinitelyBinary() { + finishIfNecessary(); + return !isTextMimeType && (!isPossibleTextMimeType || (isGuessed && isFaulty)); + } + + public boolean isProbablyText() { + return isTextMimeType || isPossibleTextMimeType && (!isGuessed || !isFaulty); + } +} 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 80f9d6368..ff9377581 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -23,13 +23,6 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -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 java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; import android.content.ClipDescription; @@ -75,14 +68,9 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; public class InputDataOperation extends BaseOperation { private final byte[] buf = new byte[256]; - private final ByteBuffer bufWrap; - private final CharBuffer dummyOutput; public InputDataOperation(Context context, ProviderHelper providerHelper, Progressable progressable) { super(context, providerHelper, progressable); - - bufWrap = ByteBuffer.wrap(buf); - dummyOutput = CharBuffer.allocate(256); } Uri mSignedDataUri; @@ -338,83 +326,37 @@ public class InputDataOperation extends BaseOperation { throw new IOException("Error getting file for writing!"); } - boolean isPossibleTextMimeType = ClipDescription.compareMimeTypes(mimeType, "application/octet-stream") - || ClipDescription.compareMimeTypes(mimeType, "application/x-download") - || ClipDescription.compareMimeTypes(mimeType, "text/*"); - // 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; - boolean charsetIsFaulty; - boolean charsetIsGuessed; - CharsetDecoder charsetDecoder = null; - if (isPossibleTextMimeType) { - charset = bd.getCharset(); - // the charset defaults to us-ascii, but we want to default to utf-8 - if (charset == null || "us-ascii".equals(charset)) { - charset = "utf-8"; - charsetIsGuessed = true; - } else { - charsetIsGuessed = false; - } - - try { - charsetDecoder = Charset.forName(charset).newDecoder(); - charsetDecoder.onMalformedInput(CodingErrorAction.REPORT); - charsetDecoder.onUnmappableCharacter(CodingErrorAction.REPORT); - charsetDecoder.reset(); - charsetIsFaulty = false; - } catch (UnsupportedCharsetException e) { - charsetIsFaulty = true; - } - } else { - charsetIsFaulty = true; - charsetIsGuessed = false; - charset = null; - } + String charset = bd.getCharset(); + CharsetVerifier charsetVerifier = new CharsetVerifier(buf, mimeType, charset); int totalLength = 0; do { totalLength += len; out.write(buf, 0, len); - - if (isPossibleTextMimeType && !charsetIsFaulty) { - bufWrap.rewind(); - bufWrap.limit(len); - dummyOutput.rewind(); - CoderResult result = charsetDecoder.decode(bufWrap, dummyOutput, false); - if (result.isError()) { - charsetIsFaulty = true; - } - } + charsetVerifier.write(0, len); } while ((len = is.read(buf)) > 0); - if (!charsetIsFaulty) { - bufWrap.rewind(); - bufWrap.limit(0); - dummyOutput.rewind(); - CoderResult result = charsetDecoder.decode(bufWrap, dummyOutput, true); - if (result.isError()) { - charsetIsFaulty = true; - } - } + log.add(LogType.MSG_DATA_MIME_LENGTH, 3, Long.toString(totalLength)); - if (isPossibleTextMimeType) { - if (charsetIsFaulty && charsetIsGuessed) { - log.add(LogType.MSG_DATA_MIME_CHARSET_UNKNOWN, 3, charset); - charset = null; - } else if (charsetIsFaulty) { - log.add(LogType.MSG_DATA_MIME_CHARSET_FAULTY, 3, charset); - } else if (charsetIsGuessed) { - log.add(LogType.MSG_DATA_MIME_CHARSET_GUESS, 3, charset); + 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, charset); + log.add(LogType.MSG_DATA_MIME_CHARSET, 3, charsetVerifier.getCharset()); } - } - - log.add(LogType.MSG_DATA_MIME_LENGTH, 3, Long.toString(totalLength)); - OpenPgpMetadata metadata = new OpenPgpMetadata(mFilename, mimeType, 0L, totalLength, charset); + metadata = new OpenPgpMetadata(mFilename, mimeType, 0L, totalLength, + charsetVerifier.getCharset(), charsetVerifier.isProbablyText()); + } out.close(); outputUris.add(uri); 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..59ba8df5f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -377,9 +377,11 @@ public class PgpDecryptVerifyOperation extends BaseOperation