externalize CharsetVerifier, add looksLikeText to OpenPgpMetadata object
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,13 +23,6 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
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 java.util.ArrayList;
|
||||||
|
|
||||||
import android.content.ClipDescription;
|
import android.content.ClipDescription;
|
||||||
@@ -75,14 +68,9 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
|||||||
public class InputDataOperation extends BaseOperation<InputDataParcel> {
|
public class InputDataOperation extends BaseOperation<InputDataParcel> {
|
||||||
|
|
||||||
private final byte[] buf = new byte[256];
|
private final byte[] buf = new byte[256];
|
||||||
private final ByteBuffer bufWrap;
|
|
||||||
private final CharBuffer dummyOutput;
|
|
||||||
|
|
||||||
public InputDataOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
|
public InputDataOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
|
||||||
super(context, providerHelper, progressable);
|
super(context, providerHelper, progressable);
|
||||||
|
|
||||||
bufWrap = ByteBuffer.wrap(buf);
|
|
||||||
dummyOutput = CharBuffer.allocate(256);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri mSignedDataUri;
|
Uri mSignedDataUri;
|
||||||
@@ -338,83 +326,37 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> {
|
|||||||
throw new IOException("Error getting file for writing!");
|
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
|
// 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.
|
// decoder, to see if the data is legal for the assumed charset.
|
||||||
String charset;
|
String charset = bd.getCharset();
|
||||||
boolean charsetIsFaulty;
|
CharsetVerifier charsetVerifier = new CharsetVerifier(buf, mimeType, charset);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
int totalLength = 0;
|
int totalLength = 0;
|
||||||
do {
|
do {
|
||||||
totalLength += len;
|
totalLength += len;
|
||||||
out.write(buf, 0, len);
|
out.write(buf, 0, len);
|
||||||
|
charsetVerifier.write(0, len);
|
||||||
if (isPossibleTextMimeType && !charsetIsFaulty) {
|
|
||||||
bufWrap.rewind();
|
|
||||||
bufWrap.limit(len);
|
|
||||||
dummyOutput.rewind();
|
|
||||||
CoderResult result = charsetDecoder.decode(bufWrap, dummyOutput, false);
|
|
||||||
if (result.isError()) {
|
|
||||||
charsetIsFaulty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while ((len = is.read(buf)) > 0);
|
} 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
} else {
|
|
||||||
log.add(LogType.MSG_DATA_MIME_CHARSET, 3, charset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.add(LogType.MSG_DATA_MIME_LENGTH, 3, Long.toString(totalLength));
|
log.add(LogType.MSG_DATA_MIME_LENGTH, 3, Long.toString(totalLength));
|
||||||
|
|
||||||
OpenPgpMetadata metadata = new OpenPgpMetadata(mFilename, mimeType, 0L, totalLength, 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, charsetVerifier.getCharset());
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = new OpenPgpMetadata(mFilename, mimeType, 0L, totalLength,
|
||||||
|
charsetVerifier.getCharset(), charsetVerifier.isProbablyText());
|
||||||
|
}
|
||||||
|
|
||||||
out.close();
|
out.close();
|
||||||
outputUris.add(uri);
|
outputUris.add(uri);
|
||||||
|
|||||||
@@ -377,9 +377,11 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
originalFilename = "";
|
originalFilename = "";
|
||||||
}
|
}
|
||||||
String mimeType = null;
|
String mimeType = null;
|
||||||
|
boolean looksLikeText;
|
||||||
if (literalData.getFormat() == PGPLiteralData.TEXT
|
if (literalData.getFormat() == PGPLiteralData.TEXT
|
||||||
|| literalData.getFormat() == PGPLiteralData.UTF8) {
|
|| literalData.getFormat() == PGPLiteralData.UTF8) {
|
||||||
mimeType = "text/plain";
|
mimeType = "text/plain";
|
||||||
|
looksLikeText = true;
|
||||||
} else {
|
} else {
|
||||||
// try to guess from file ending
|
// try to guess from file ending
|
||||||
String extension = MimeTypeMap.getFileExtensionFromUrl(originalFilename);
|
String extension = MimeTypeMap.getFileExtensionFromUrl(originalFilename);
|
||||||
@@ -390,6 +392,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
if (mimeType == null) {
|
if (mimeType == null) {
|
||||||
mimeType = "application/octet-stream";
|
mimeType = "application/octet-stream";
|
||||||
}
|
}
|
||||||
|
looksLikeText = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!"".equals(originalFilename)) {
|
if (!"".equals(originalFilename)) {
|
||||||
@@ -414,11 +417,9 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
}
|
}
|
||||||
|
|
||||||
metadata = new OpenPgpMetadata(
|
metadata = new OpenPgpMetadata(
|
||||||
originalFilename,
|
originalFilename, mimeType,
|
||||||
mimeType,
|
|
||||||
literalData.getModificationTime().getTime(),
|
literalData.getModificationTime().getTime(),
|
||||||
originalSize == null ? 0 : originalSize,
|
originalSize == null ? 0 : originalSize, charset, false);
|
||||||
charset);
|
|
||||||
|
|
||||||
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
|
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
|
||||||
DecryptVerifyResult result =
|
DecryptVerifyResult result =
|
||||||
@@ -490,8 +491,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
|
|
||||||
log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, mimeType);
|
log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1, mimeType);
|
||||||
|
|
||||||
metadata = new OpenPgpMetadata(
|
metadata = new OpenPgpMetadata(originalFilename, mimeType, literalData.getModificationTime().getTime(),
|
||||||
originalFilename, mimeType, literalData.getModificationTime().getTime(), alreadyWritten, charset);
|
alreadyWritten, charset, looksLikeText);
|
||||||
|
|
||||||
indent -= 1;
|
indent -= 1;
|
||||||
|
|
||||||
@@ -873,11 +874,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
|
|
||||||
log.add(LogType.MSG_DC_OK, indent);
|
log.add(LogType.MSG_DC_OK, indent);
|
||||||
|
|
||||||
OpenPgpMetadata metadata = new OpenPgpMetadata(
|
OpenPgpMetadata metadata = new OpenPgpMetadata("", "text/plain", -1, clearText.length, "utf-8", true);
|
||||||
"",
|
|
||||||
"text/plain",
|
|
||||||
-1,
|
|
||||||
clearText.length);
|
|
||||||
|
|
||||||
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
|
||||||
result.setSignatureResult(signatureChecker.getSignatureResult());
|
result.setSignatureResult(signatureChecker.getSignatureResult());
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ public class InputDataOperationTest {
|
|||||||
OpenPgpMetadata metadata = result.mMetadata.get(0);
|
OpenPgpMetadata metadata = result.mMetadata.get(0);
|
||||||
Assert.assertEquals("text/plain", metadata.getMimeType());
|
Assert.assertEquals("text/plain", metadata.getMimeType());
|
||||||
Assert.assertEquals("utf-8", metadata.getCharset());
|
Assert.assertEquals("utf-8", metadata.getCharset());
|
||||||
|
Assert.assertTrue("data should be looksLikeText", metadata.isLooksLikeText());
|
||||||
|
|
||||||
metadata = result.mMetadata.get(1);
|
metadata = result.mMetadata.get(1);
|
||||||
Assert.assertEquals("text/testvalue", metadata.getMimeType());
|
Assert.assertEquals("text/testvalue", metadata.getMimeType());
|
||||||
@@ -213,6 +214,7 @@ public class InputDataOperationTest {
|
|||||||
|
|
||||||
OpenPgpMetadata metadata = result.mMetadata.get(0);
|
OpenPgpMetadata metadata = result.mMetadata.get(0);
|
||||||
Assert.assertEquals("text/plain", metadata.getMimeType());
|
Assert.assertEquals("text/plain", metadata.getMimeType());
|
||||||
|
Assert.assertTrue("data should be looksLikeText", metadata.isLooksLikeText());
|
||||||
|
|
||||||
Assert.assertNull("charset was bad so it should not be set", metadata.getCharset());
|
Assert.assertNull("charset was bad so it should not be set", metadata.getCharset());
|
||||||
Assert.assertTrue("faulty charset should have been detected",
|
Assert.assertTrue("faulty charset should have been detected",
|
||||||
@@ -234,6 +236,7 @@ public class InputDataOperationTest {
|
|||||||
|
|
||||||
OpenPgpMetadata metadata = result.mMetadata.get(0);
|
OpenPgpMetadata metadata = result.mMetadata.get(0);
|
||||||
Assert.assertEquals("text/plain", metadata.getMimeType());
|
Assert.assertEquals("text/plain", metadata.getMimeType());
|
||||||
|
Assert.assertTrue("data should be looksLikeText", metadata.isLooksLikeText());
|
||||||
|
|
||||||
Assert.assertEquals("charset should be set since it was guessed and not faulty",
|
Assert.assertEquals("charset should be set since it was guessed and not faulty",
|
||||||
"utf-8", metadata.getCharset());
|
"utf-8", metadata.getCharset());
|
||||||
@@ -256,6 +259,7 @@ public class InputDataOperationTest {
|
|||||||
|
|
||||||
OpenPgpMetadata metadata = result.mMetadata.get(0);
|
OpenPgpMetadata metadata = result.mMetadata.get(0);
|
||||||
Assert.assertEquals("application/octet-stream", metadata.getMimeType());
|
Assert.assertEquals("application/octet-stream", metadata.getMimeType());
|
||||||
|
Assert.assertTrue("data should be looksLikeText", metadata.isLooksLikeText());
|
||||||
|
|
||||||
Assert.assertEquals("charset should be set since it was guessed and not faulty",
|
Assert.assertEquals("charset should be set since it was guessed and not faulty",
|
||||||
"utf-8", metadata.getCharset());
|
"utf-8", metadata.getCharset());
|
||||||
|
|||||||
2
extern/openpgp-api-lib
vendored
2
extern/openpgp-api-lib
vendored
Submodule extern/openpgp-api-lib updated: 075616c461...51bbe35aa1
Reference in New Issue
Block a user