New decrypt activity design (WIP), saner UTF8 decoding (replacing non-decodable characters)

This commit is contained in:
Dominik Schürmann
2014-09-15 10:19:55 +02:00
parent 88bbce831c
commit 53bc417f8f
19 changed files with 549 additions and 324 deletions

View File

@@ -42,6 +42,7 @@ import org.sufficientlysecure.keychain.service.results.OperationResultParcel.Log
import org.sufficientlysecure.keychain.service.results.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Utf8Util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -365,7 +366,7 @@ public class UncachedKeyRing {
ArrayList<byte[]> processedUserIds = new ArrayList<byte[]>();
for (byte[] rawUserId : new IterableIterator<byte[]>(masterKey.getRawUserIDs())) {
String userId = Strings.fromUTF8ByteArray(rawUserId);
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
// check for duplicate user ids
if (processedUserIds.contains(rawUserId)) {
@@ -439,7 +440,7 @@ public class UncachedKeyRing {
continue;
}
// warn user if the signature was made with bad encoding
if (!cert.verifySignature(masterKey, userId)) {
if (!Utf8Util.isValidUTF8(rawUserId)) {
log.add(LogLevel.WARN, LogType.MSG_KC_UID_WARN_ENCODING, indent);
}
} catch (PgpGeneralException e) {

View File

@@ -32,6 +32,7 @@ import org.spongycastle.util.Strings;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Utf8Util;
import java.util.ArrayList;
import java.util.Arrays;
@@ -185,7 +186,7 @@ public class UncachedPublicKey {
}
}
if (found != null) {
return Strings.fromUTF8ByteArray(found);
return Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(found);
} else {
return null;
}
@@ -204,8 +205,9 @@ public class UncachedPublicKey {
public ArrayList<String> getUnorderedUserIds() {
ArrayList<String> userIds = new ArrayList<String>();
for (String userId : new IterableIterator<String>(mPublicKey.getUserIDs())) {
userIds.add(userId);
for (byte[] rawUserId : new IterableIterator<byte[]>(mPublicKey.getRawUserIDs())) {
// use our decoding method
userIds.add(Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId));
}
return userIds;
}

View File

@@ -65,6 +65,7 @@ import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressFixedScaler;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import org.sufficientlysecure.keychain.util.Utf8Util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -437,8 +438,7 @@ public class ProviderHelper {
List<UserIdItem> uids = new ArrayList<UserIdItem>();
for (byte[] rawUserId : new IterableIterator<byte[]>(
masterKey.getUnorderedRawUserIds().iterator())) {
String userId = Strings.fromUTF8ByteArray(rawUserId);
Log.d(Constants.TAG, "userId: "+userId);
String userId = Utf8Util.fromUTF8ByteArrayReplaceBadEncoding(rawUserId);
UserIdItem item = new UserIdItem();
uids.add(item);

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import org.sufficientlysecure.keychain.R;
public class DecryptActivity extends DrawerActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.decrypt_activity);
activateDrawerNavigation(savedInstanceState);
View actionFile = findViewById(R.id.decrypt_files);
View actionFromClipboard = findViewById(R.id.decrypt_from_clipboard);
actionFile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent filesDecrypt = new Intent(DecryptActivity.this, DecryptFilesActivity.class);
filesDecrypt.setAction(DecryptFilesActivity.ACTION_DECRYPT_DATA_OPEN);
startActivity(filesDecrypt);
}
});
actionFromClipboard.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent clipboardDecrypt = new Intent(DecryptActivity.this, DecryptTextActivity.class);
clipboardDecrypt.setAction(DecryptTextActivity.ACTION_DECRYPT_FROM_CLIPBOARD);
startActivity(clipboardDecrypt);
}
});
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
import org.sufficientlysecure.keychain.util.Log;
public class DecryptFilesActivity extends ActionBarActivity {
/* Intents */
public static final String ACTION_DECRYPT_DATA = OpenKeychainIntents.DECRYPT_DATA;
// intern
public static final String ACTION_DECRYPT_DATA_OPEN = Constants.INTENT_PREFIX + "DECRYPT_DATA_OPEN";
DecryptFilesFragment mFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.decrypt_files_activity);
// Handle intent actions
handleActions(savedInstanceState, getIntent());
}
/**
* Handles all actions with this intent
*
* @param intent
*/
private void handleActions(Bundle savedInstanceState, Intent intent) {
String action = intent.getAction();
String type = intent.getType();
Uri uri = intent.getData();
Bundle mFileFragmentBundle = new Bundle();
/*
* Android's Action
*/
if (Intent.ACTION_SEND.equals(action) && type != null) {
// When sending to Keychain Decrypt via share menu
// Binary via content provider (could also be files)
// override uri to get stream from send
uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
action = ACTION_DECRYPT_DATA;
} else if (Intent.ACTION_VIEW.equals(action)) {
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
// override action
action = ACTION_DECRYPT_DATA;
}
/**
* Main Actions
*/
if (ACTION_DECRYPT_DATA.equals(action) && uri != null) {
mFileFragmentBundle.putParcelable(DecryptFilesFragment.ARG_URI, uri);
loadFragment(savedInstanceState, uri, false);
} else if (ACTION_DECRYPT_DATA_OPEN.equals(action)) {
loadFragment(savedInstanceState, null, true);
} else if (ACTION_DECRYPT_DATA.equals(action)) {
Log.e(Constants.TAG,
"Include an Uri with setData() in your Intent!");
}
}
private void loadFragment(Bundle savedInstanceState, Uri uri, boolean openDialog) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create an instance of the fragment
mFragment = DecryptFilesFragment.newInstance(uri, openDialog);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
.replace(R.id.decrypt_files_fragment_container, mFragment)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
}
}

View File

@@ -46,9 +46,10 @@ import org.sufficientlysecure.keychain.util.Notify;
import java.io.File;
public class DecryptFileFragment extends DecryptFragment {
public class DecryptFilesFragment extends DecryptFragment {
public static final String ARG_URI = "uri";
public static final String ARG_FROM_VIEW_INTENT = "view_intent";
// public static final String ARG_FROM_VIEW_INTENT = "view_intent";
public static final String ARG_OPEN_DIRECTLY = "open_directly";
private static final int REQUEST_CODE_INPUT = 0x00007003;
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
@@ -62,6 +63,22 @@ public class DecryptFileFragment extends DecryptFragment {
private Uri mInputUri = null;
private Uri mOutputUri = null;
/**
* Creates new instance of this fragment
*/
public static DecryptFilesFragment newInstance(Uri uri, boolean openDirectly) {
DecryptFilesFragment frag = new DecryptFilesFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_URI, uri);
// args.putBoolean(ARG_FROM_VIEW_INTENT, fromViewIntent);
args.putBoolean(ARG_OPEN_DIRECTLY, openDirectly);
frag.setArguments(args);
return frag;
}
/**
* Inflate the layout for this fragment
*/
@@ -75,9 +92,9 @@ public class DecryptFileFragment extends DecryptFragment {
view.findViewById(R.id.decrypt_file_browse).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
FileHelper.openDocument(DecryptFileFragment.this, "*/*", REQUEST_CODE_INPUT);
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
} else {
FileHelper.openFile(DecryptFileFragment.this, mInputUri, "*/*",
FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*",
REQUEST_CODE_INPUT);
}
}
@@ -97,6 +114,15 @@ public class DecryptFileFragment extends DecryptFragment {
super.onActivityCreated(savedInstanceState);
setInputUri(getArguments().<Uri>getParcelable(ARG_URI));
if (getArguments().getBoolean(ARG_OPEN_DIRECTLY, false)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
FileHelper.openDocument(DecryptFilesFragment.this, "*/*", REQUEST_CODE_INPUT);
} else {
FileHelper.openFile(DecryptFilesFragment.this, mInputUri, "*/*",
REQUEST_CODE_INPUT);
}
}
}
private void setInputUri(Uri inputUri) {

View File

@@ -19,83 +19,56 @@
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.view.PagerTabStrip;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBarActivity;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.api.OpenKeychainIntents;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Notify;
import java.util.regex.Matcher;
public class DecryptOldActivity extends DrawerActivity {
public class DecryptTextActivity extends ActionBarActivity {
/* Intents */
public static final String ACTION_DECRYPT = OpenKeychainIntents.DECRYPT;
/* EXTRA keys for input */
public static final String ACTION_DECRYPT_TEXT = OpenKeychainIntents.DECRYPT_TEXT;
public static final String EXTRA_TEXT = OpenKeychainIntents.DECRYPT_EXTRA_TEXT;
ViewPager mViewPager;
PagerTabStrip mPagerTabStrip;
PagerTabStripAdapter mTabsAdapter;
// intern
public static final String ACTION_DECRYPT_FROM_CLIPBOARD = Constants.INTENT_PREFIX + "DECRYPT_TEXT_FROM_CLIPBOARD";
Bundle mMessageFragmentBundle = new Bundle();
Bundle mFileFragmentBundle = new Bundle();
int mSwitchToTab = PAGER_TAB_MESSAGE;
private static final int PAGER_TAB_MESSAGE = 0;
private static final int PAGER_TAB_FILE = 1;
private void initView() {
mViewPager = (ViewPager) findViewById(R.id.decrypt_pager);
mPagerTabStrip = (PagerTabStrip) findViewById(R.id.decrypt_pager_tab_strip);
mTabsAdapter = new PagerTabStripAdapter(this);
mViewPager.setAdapter(mTabsAdapter);
}
DecryptTextFragment mFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.decrypt_activity_old);
setContentView(R.layout.decrypt_text_activity);
initView();
activateDrawerNavigation(savedInstanceState);
// Handle intent actions, maybe changes the bundles
handleActions(getIntent());
mTabsAdapter.addTab(DecryptMessageFragment.class,
mMessageFragmentBundle, getString(R.string.label_message));
mTabsAdapter.addTab(DecryptFileFragment.class,
mFileFragmentBundle, getString(R.string.label_file));
mViewPager.setCurrentItem(mSwitchToTab);
// Handle intent actions
handleActions(savedInstanceState, getIntent());
}
/**
* Handles all actions with this intent
*
* @param intent
*/
private void handleActions(Intent intent) {
private void handleActions(Bundle savedInstanceState, Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
String type = intent.getType();
Uri uri = intent.getData();
if (extras == null) {
extras = new Bundle();
}
String textData = null;
/*
* Android's Action
*/
@@ -106,30 +79,17 @@ public class DecryptOldActivity extends DrawerActivity {
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (sharedText != null) {
// handle like normal text decryption, override action and extras to later
// executeServiceMethod ACTION_DECRYPT in main actions
extras.putString(EXTRA_TEXT, sharedText);
action = ACTION_DECRYPT;
// executeServiceMethod ACTION_DECRYPT_TEXT in main actions
textData = sharedText;
}
} else {
// Binary via content provider (could also be files)
// override uri to get stream from send
uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
action = ACTION_DECRYPT;
}
} else if (Intent.ACTION_VIEW.equals(action)) {
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
// override action
action = ACTION_DECRYPT;
mFileFragmentBundle.putBoolean(DecryptFileFragment.ARG_FROM_VIEW_INTENT, true);
}
String textData = extras.getString(EXTRA_TEXT);
/**
* Main Actions
*/
if (ACTION_DECRYPT.equals(action) && textData != null) {
textData = extras.getString(EXTRA_TEXT);
if (ACTION_DECRYPT_TEXT.equals(action) && textData != null) {
Log.d(Constants.TAG, "textData not null, matching text ...");
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(textData);
if (matcher.matches()) {
@@ -137,9 +97,6 @@ public class DecryptOldActivity extends DrawerActivity {
textData = matcher.group(1);
// replace non breakable spaces
textData = textData.replaceAll("\\xa0", " ");
mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData);
mSwitchToTab = PAGER_TAB_MESSAGE;
} else {
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(textData);
if (matcher.matches()) {
@@ -147,20 +104,55 @@ public class DecryptOldActivity extends DrawerActivity {
textData = matcher.group(1);
// replace non breakable spaces
textData = textData.replaceAll("\\xa0", " ");
mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData);
mSwitchToTab = PAGER_TAB_MESSAGE;
} else {
Notify.showNotify(this, R.string.error_invalid_data, Notify.Style.ERROR);
Log.d(Constants.TAG, "Nothing matched!");
}
}
} else if (ACTION_DECRYPT.equals(action) && uri != null) {
mFileFragmentBundle.putParcelable(DecryptFileFragment.ARG_URI, uri);
mSwitchToTab = PAGER_TAB_FILE;
} else if (ACTION_DECRYPT.equals(action)) {
} else if (ACTION_DECRYPT_FROM_CLIPBOARD.equals(action)) {
CharSequence clipboardText = ClipboardReflection.getClipboardText(this);
// only decrypt if clipboard content is available and a pgp message or cleartext signature
if (clipboardText != null) {
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText);
if (!matcher.matches()) {
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText);
}
if (matcher.matches()) {
textData = matcher.group(1);
} else {
Notify.showNotify(this, R.string.error_invalid_data, Notify.Style.ERROR);
}
} else {
Notify.showNotify(this, R.string.error_invalid_data, Notify.Style.ERROR);
}
} else if (ACTION_DECRYPT_TEXT.equals(action)) {
Log.e(Constants.TAG,
"Include the extra 'text' or an Uri with setData() in your Intent!");
"Include the extra 'text' in your Intent!");
}
loadFragment(savedInstanceState, textData);
}
private void loadFragment(Bundle savedInstanceState, String ciphertext) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
// Create an instance of the fragment
mFragment = DecryptTextFragment.newInstance(ciphertext);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
.replace(R.id.decrypt_text_fragment_container, mFragment)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
}
}

View File

@@ -24,62 +24,70 @@ import android.os.Message;
import android.os.Messenger;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.service.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.results.DecryptVerifyResult;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Notify;
import java.util.regex.Matcher;
public class DecryptMessageFragment extends DecryptFragment {
public class DecryptTextFragment extends DecryptFragment {
public static final String ARG_CIPHERTEXT = "ciphertext";
// view
private EditText mMessage;
private View mDecryptButton;
private View mDecryptFromCLipboardButton;
// model
// // view
private TextView mMessage;
// private View mDecryptButton;
// private View mDecryptFromCLipboardButton;
//
// // model
private String mCiphertext;
/**
* Creates new instance of this fragment
*/
public static DecryptTextFragment newInstance(String ciphertext) {
DecryptTextFragment frag = new DecryptTextFragment();
Bundle args = new Bundle();
args.putString(ARG_CIPHERTEXT, ciphertext);
frag.setArguments(args);
return frag;
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.decrypt_message_fragment, container, false);
View view = inflater.inflate(R.layout.decrypt_text_fragment, container, false);
mMessage = (EditText) view.findViewById(R.id.message);
mDecryptButton = view.findViewById(R.id.action_decrypt);
mDecryptFromCLipboardButton = view.findViewById(R.id.action_decrypt_from_clipboard);
mDecryptButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
decryptClicked();
}
});
mDecryptFromCLipboardButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
decryptFromClipboardClicked();
}
});
mMessage = (TextView) view.findViewById(R.id.decrypt_text_plaintext);
// mDecryptButton = view.findViewById(R.id.action_decrypt);
// mDecryptFromCLipboardButton = view.findViewById(R.id.action_decrypt_from_clipboard);
// mDecryptButton.setOnClickListener(new OnClickListener() {
// @Override
// public void onClick(View v) {
// decryptClicked();
// }
// });
// mDecryptFromCLipboardButton.setOnClickListener(new OnClickListener() {
// @Override
// public void onClick(View v) {
// decryptFromClipboardClicked();
// }
// });
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String ciphertext = getArguments().getString(ARG_CIPHERTEXT);
if (ciphertext != null) {
@@ -88,31 +96,6 @@ public class DecryptMessageFragment extends DecryptFragment {
}
}
private void decryptClicked() {
mCiphertext = mMessage.getText().toString();
decryptStart(null);
}
private void decryptFromClipboardClicked() {
CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
// only decrypt if clipboard content is available and a pgp message or cleartext signature
if (clipboardText != null) {
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText);
if (!matcher.matches()) {
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText);
}
if (matcher.matches()) {
mCiphertext = matcher.group(1);
decryptStart(null);
} else {
Notify.showNotify(getActivity(), R.string.error_invalid_data, Notify.Style.ERROR);
}
} else {
Notify.showNotify(getActivity(), R.string.error_invalid_data, Notify.Style.ERROR);
}
}
@Override
protected void decryptStart(String passphrase) {
Log.d(Constants.TAG, "decryptStart");

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.util;
import org.sufficientlysecure.keychain.Constants;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
public class Utf8Util {
public static boolean isValidUTF8(byte[] input) {
CharsetDecoder cs = Charset.forName("UTF-8").newDecoder();
try {
cs.decode(ByteBuffer.wrap(input));
return true;
} catch (CharacterCodingException e) {
return false;
}
}
public static String fromUTF8ByteArrayReplaceBadEncoding(byte[] input) {
final CharsetDecoder charsetDecoder = Charset.forName("UTF-8").newDecoder();
charsetDecoder.onMalformedInput(CodingErrorAction.REPLACE);
charsetDecoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
try {
return charsetDecoder.decode(ByteBuffer.wrap(input)).toString();
} catch (CharacterCodingException e) {
Log.e(Constants.TAG, "Decoding failed!", e);
return charsetDecoder.replacement();
}
}
}