1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-27 03:32:16 -05:00

Merge remote-tracking branch 'k9mail_pgp_mime/master'

Conflicts:
	src/com/fsck/k9/activity/AccessibleEmailContentActivity.java
This commit is contained in:
cketti 2014-11-12 19:22:53 +01:00
commit 9b61fe0f0e
95 changed files with 5992 additions and 5676 deletions

View File

@ -31,9 +31,9 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store; import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Folder.FolderClass; import com.fsck.k9.mail.Folder.FolderClass;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.StorageManager.StorageProvider; import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.provider.EmailProvider; import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.StatsColumns; import com.fsck.k9.provider.EmailProvider.StatsColumns;
import com.fsck.k9.search.ConditionsTreeNode; import com.fsck.k9.search.ConditionsTreeNode;
@ -181,7 +181,7 @@ public class Account implements BaseAccount {
private boolean mPushPollOnConnect; private boolean mPushPollOnConnect;
private boolean mNotifySync; private boolean mNotifySync;
private SortType mSortType; private SortType mSortType;
private HashMap<SortType, Boolean> mSortAscending = new HashMap<SortType, Boolean>(); private Map<SortType, Boolean> mSortAscending = new HashMap<SortType, Boolean>();
private ShowPictures mShowPictures; private ShowPictures mShowPictures;
private boolean mIsSignatureBeforeQuotedText; private boolean mIsSignatureBeforeQuotedText;
private String mExpungePolicy = EXPUNGE_IMMEDIATELY; private String mExpungePolicy = EXPUNGE_IMMEDIATELY;
@ -338,7 +338,7 @@ public class Account implements BaseAccount {
* Pick a nice Android guidelines color if we haven't used them all yet. * Pick a nice Android guidelines color if we haven't used them all yet.
*/ */
private int pickColor(Context context) { private int pickColor(Context context) {
Account[] accounts = Preferences.getPreferences(context).getAccounts(); List<Account> accounts = Preferences.getPreferences(context).getAccounts();
List<Integer> availableColors = new ArrayList<Integer>(PREDEFINED_COLORS.length); List<Integer> availableColors = new ArrayList<Integer>(PREDEFINED_COLORS.length);
Collections.addAll(availableColors, PREDEFINED_COLORS); Collections.addAll(availableColors, PREDEFINED_COLORS);
@ -620,8 +620,8 @@ public class Account implements BaseAccount {
} }
public static List<Integer> getExistingAccountNumbers(Preferences preferences) { public static List<Integer> getExistingAccountNumbers(Preferences preferences) {
Account[] accounts = preferences.getAccounts(); List<Account> accounts = preferences.getAccounts();
List<Integer> accountNumbers = new ArrayList<Integer>(accounts.length); List<Integer> accountNumbers = new ArrayList<Integer>(accounts.size());
for (Account a : accounts) { for (Account a : accounts) {
accountNumbers.add(a.getAccountNumber()); accountNumbers.add(a.getAccountNumber());
} }
@ -679,10 +679,10 @@ public class Account implements BaseAccount {
* *
* I bet there is a much smarter way to do this. Anyone like to suggest it? * I bet there is a much smarter way to do this. Anyone like to suggest it?
*/ */
Account[] accounts = preferences.getAccounts(); List<Account> accounts = preferences.getAccounts();
int[] accountNumbers = new int[accounts.length]; int[] accountNumbers = new int[accounts.size()];
for (int i = 0; i < accounts.length; i++) { for (int i = 0; i < accounts.size(); i++) {
accountNumbers[i] = accounts[i].getAccountNumber(); accountNumbers[i] = accounts.get(i).getAccountNumber();
} }
Arrays.sort(accountNumbers); Arrays.sort(accountNumbers);
for (int accountNumber : accountNumbers) { for (int accountNumber : accountNumbers) {

View File

@ -5,6 +5,7 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue; import java.util.concurrent.SynchronousQueue;
@ -35,7 +36,7 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody; import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.provider.UnreadWidgetProvider; import com.fsck.k9.provider.UnreadWidgetProvider;
import com.fsck.k9.security.LocalKeyStore; import com.fsck.k9.security.LocalKeyStore;
import com.fsck.k9.service.BootReceiver; import com.fsck.k9.service.BootReceiver;
@ -88,7 +89,7 @@ public class K9 extends Application {
* *
* @see ApplicationAware * @see ApplicationAware
*/ */
private static List<ApplicationAware> observers = new ArrayList<ApplicationAware>(); private static final List<ApplicationAware> observers = new ArrayList<ApplicationAware>();
/** /**
* This will be {@code true} once the initialization is complete and {@link #notifyObservers()} * This will be {@code true} once the initialization is complete and {@link #notifyObservers()}
@ -263,7 +264,7 @@ public class K9 extends Application {
private static boolean mHideTimeZone = false; private static boolean mHideTimeZone = false;
private static SortType mSortType; private static SortType mSortType;
private static HashMap<SortType, Boolean> mSortAscending = new HashMap<SortType, Boolean>(); private static Map<SortType, Boolean> mSortAscending = new HashMap<SortType, Boolean>();
private static boolean sUseBackgroundAsUnreadIndicator = true; private static boolean sUseBackgroundAsUnreadIndicator = true;
private static boolean sThreadedViewEnabled = true; private static boolean sThreadedViewEnabled = true;

View File

@ -19,7 +19,7 @@ import java.io.DataOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.Provider; import java.security.Provider;
import java.security.SecureRandom; import java.security.SecureRandom;
@ -284,10 +284,7 @@ public final class PRNGFixes {
if (serial != null) { if (serial != null) {
result.append(serial); result.append(serial);
} }
try {
return result.toString().getBytes("UTF-8"); return result.toString().getBytes(Charset.forName("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 encoding not supported");
}
} }
} }

View File

@ -3,6 +3,7 @@ package com.fsck.k9;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -18,11 +19,6 @@ import com.fsck.k9.preferences.Storage;
public class Preferences { public class Preferences {
/**
* Immutable empty {@link Account} array
*/
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[0];
private static Preferences preferences; private static Preferences preferences;
public static synchronized Preferences getPreferences(Context context) { public static synchronized Preferences getPreferences(Context context) {
@ -43,7 +39,7 @@ public class Preferences {
private Preferences(Context context) { private Preferences(Context context) {
mStorage = Storage.getStorage(context); mStorage = Storage.getStorage(context);
mContext = context; mContext = context;
if (mStorage.size() == 0) { if (mStorage.isEmpty()) {
Log.i(K9.LOG_TAG, "Preferences storage is zero-size, importing from Android-style preferences"); Log.i(K9.LOG_TAG, "Preferences storage is zero-size, importing from Android-style preferences");
Editor editor = mStorage.edit(); Editor editor = mStorage.edit();
editor.copy(context.getSharedPreferences("AndroidMail.Main", Context.MODE_PRIVATE)); editor.copy(context.getSharedPreferences("AndroidMail.Main", Context.MODE_PRIVATE));
@ -75,12 +71,12 @@ public class Preferences {
* registered the method returns an empty array. * registered the method returns an empty array.
* @return all accounts * @return all accounts
*/ */
public synchronized Account[] getAccounts() { public synchronized List<Account> getAccounts() {
if (accounts == null) { if (accounts == null) {
loadAccounts(); loadAccounts();
} }
return accountsInOrder.toArray(EMPTY_ACCOUNT_ARRAY); return Collections.unmodifiableList(accountsInOrder);
} }
/** /**
@ -89,7 +85,7 @@ public class Preferences {
* @return all accounts with {@link Account#isAvailable(Context)} * @return all accounts with {@link Account#isAvailable(Context)}
*/ */
public synchronized Collection<Account> getAvailableAccounts() { public synchronized Collection<Account> getAvailableAccounts() {
Account[] allAccounts = getAccounts(); List<Account> allAccounts = getAccounts();
Collection<Account> retval = new ArrayList<Account>(accounts.size()); Collection<Account> retval = new ArrayList<Account>(accounts.size());
for (Account account : allAccounts) { for (Account account : allAccounts) {
if (account.isEnabled() && account.isAvailable(mContext)) { if (account.isEnabled() && account.isAvailable(mContext)) {

View File

@ -68,7 +68,7 @@ public abstract class AccountList extends K9ListActivity implements OnItemClickL
* @param realAccounts * @param realAccounts
* An array of accounts to display. * An array of accounts to display.
*/ */
public void populateListView(Account[] realAccounts) { public void populateListView(List<Account> realAccounts) {
List<BaseAccount> accounts = new ArrayList<BaseAccount>(); List<BaseAccount> accounts = new ArrayList<BaseAccount>();
if (displaySpecialAccounts() && !K9.isHideSpecialAccounts()) { if (displaySpecialAccounts() && !K9.isHideSpecialAccounts()) {
@ -79,7 +79,7 @@ public abstract class AccountList extends K9ListActivity implements OnItemClickL
accounts.add(allMessagesAccount); accounts.add(allMessagesAccount);
} }
accounts.addAll(Arrays.asList(realAccounts)); accounts.addAll(realAccounts);
AccountsAdapter adapter = new AccountsAdapter(accounts); AccountsAdapter adapter = new AccountsAdapter(accounts);
ListView listView = getListView(); ListView listView = getListView();
listView.setAdapter(adapter); listView.setAdapter(adapter);
@ -169,15 +169,15 @@ public abstract class AccountList extends K9ListActivity implements OnItemClickL
/** /**
* Load accounts in a background thread * Load accounts in a background thread
*/ */
class LoadAccounts extends AsyncTask<Void, Void, Account[]> { class LoadAccounts extends AsyncTask<Void, Void, List<Account>> {
@Override @Override
protected Account[] doInBackground(Void... params) { protected List<Account> doInBackground(Void... params) {
Account[] accounts = Preferences.getPreferences(getApplicationContext()).getAccounts(); List<Account> accounts = Preferences.getPreferences(getApplicationContext()).getAccounts();
return accounts; return accounts;
} }
@Override @Override
protected void onPostExecute(Account[] accounts) { protected void onPostExecute(List<Account> accounts) {
populateListView(accounts); populateListView(accounts);
} }
} }

View File

@ -13,6 +13,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import android.app.ActionBar; import android.app.ActionBar;
import android.app.Activity; import android.app.Activity;
@ -75,6 +76,7 @@ import com.fsck.k9.activity.setup.Prefs;
import com.fsck.k9.activity.setup.WelcomeMessage; import com.fsck.k9.activity.setup.WelcomeMessage;
import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.SizeFormatter; import com.fsck.k9.helper.SizeFormatter;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.AuthType; import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Store; import com.fsck.k9.mail.Store;
@ -100,11 +102,6 @@ import de.cketti.library.changelog.ChangeLog;
public class Accounts extends K9ListActivity implements OnItemClickListener { public class Accounts extends K9ListActivity implements OnItemClickListener {
/**
* Immutable empty {@link BaseAccount} array
*/
private static final BaseAccount[] EMPTY_BASE_ACCOUNT_ARRAY = new BaseAccount[0];
/** /**
* URL used to open Android Market application * URL used to open Android Market application
*/ */
@ -120,9 +117,12 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
private static final int DIALOG_RECREATE_ACCOUNT = 3; private static final int DIALOG_RECREATE_ACCOUNT = 3;
private static final int DIALOG_NO_FILE_MANAGER = 4; private static final int DIALOG_NO_FILE_MANAGER = 4;
/*
* Must be serializable hence implementation class used for declaration.
*/
private ConcurrentHashMap<String, AccountStats> accountStats = new ConcurrentHashMap<String, AccountStats>(); private ConcurrentHashMap<String, AccountStats> accountStats = new ConcurrentHashMap<String, AccountStats>();
private ConcurrentHashMap<BaseAccount, String> pendingWork = new ConcurrentHashMap<BaseAccount, String>(); private ConcurrentMap<BaseAccount, String> pendingWork = new ConcurrentHashMap<BaseAccount, String>();
private BaseAccount mSelectedContextAccount; private BaseAccount mSelectedContextAccount;
private int mUnreadMessageCount = 0; private int mUnreadMessageCount = 0;
@ -398,14 +398,14 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
createSpecialAccounts(); createSpecialAccounts();
} }
Account[] accounts = Preferences.getPreferences(this).getAccounts(); List<Account> accounts = Preferences.getPreferences(this).getAccounts();
Intent intent = getIntent(); Intent intent = getIntent();
//onNewIntent(intent); //onNewIntent(intent);
// see if we should show the welcome message // see if we should show the welcome message
if (ACTION_IMPORT_SETTINGS.equals(intent.getAction())) { if (ACTION_IMPORT_SETTINGS.equals(intent.getAction())) {
onImport(); onImport();
} else if (accounts.length < 1) { } else if (accounts.size() < 1) {
WelcomeMessage.showWelcomeMessage(this); WelcomeMessage.showWelcomeMessage(this);
finish(); finish();
return; return;
@ -421,7 +421,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
onOpenAccount(mUnifiedInboxAccount); onOpenAccount(mUnifiedInboxAccount);
finish(); finish();
return; return;
} else if (startup && accounts.length == 1 && onOpenAccount(accounts[0])) { } else if (startup && accounts.size() == 1 && onOpenAccount(accounts.get(0))) {
finish(); finish();
return; return;
} }
@ -541,18 +541,18 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
return retain; return retain;
} }
private BaseAccount[] accounts = new BaseAccount[0]; private List<BaseAccount> accounts = new ArrayList<BaseAccount>();
private enum ACCOUNT_LOCATION { private enum ACCOUNT_LOCATION {
TOP, MIDDLE, BOTTOM; TOP, MIDDLE, BOTTOM;
} }
private EnumSet<ACCOUNT_LOCATION> accountLocation(BaseAccount account) { private EnumSet<ACCOUNT_LOCATION> accountLocation(BaseAccount account) {
EnumSet<ACCOUNT_LOCATION> accountLocation = EnumSet.of(ACCOUNT_LOCATION.MIDDLE); EnumSet<ACCOUNT_LOCATION> accountLocation = EnumSet.of(ACCOUNT_LOCATION.MIDDLE);
if (accounts.length > 0) { if (accounts.size() > 0) {
if (accounts[0].equals(account)) { if (accounts.get(0).equals(account)) {
accountLocation.remove(ACCOUNT_LOCATION.MIDDLE); accountLocation.remove(ACCOUNT_LOCATION.MIDDLE);
accountLocation.add(ACCOUNT_LOCATION.TOP); accountLocation.add(ACCOUNT_LOCATION.TOP);
} }
if (accounts[accounts.length - 1].equals(account)) { if (accounts.get(accounts.size() - 1).equals(account)) {
accountLocation.remove(ACCOUNT_LOCATION.MIDDLE); accountLocation.remove(ACCOUNT_LOCATION.MIDDLE);
accountLocation.add(ACCOUNT_LOCATION.BOTTOM); accountLocation.add(ACCOUNT_LOCATION.BOTTOM);
} }
@ -562,7 +562,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
private void refresh() { private void refresh() {
accounts = Preferences.getPreferences(this).getAccounts(); accounts.clear();
accounts.addAll(Preferences.getPreferences(this).getAccounts());
// see if we should show the welcome message // see if we should show the welcome message
// if (accounts.length < 1) { // if (accounts.length < 1) {
@ -571,22 +572,22 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
// } // }
List<BaseAccount> newAccounts; List<BaseAccount> newAccounts;
if (!K9.isHideSpecialAccounts() && accounts.length > 0) { if (!K9.isHideSpecialAccounts() && accounts.size() > 0) {
if (mUnifiedInboxAccount == null || mAllMessagesAccount == null) { if (mUnifiedInboxAccount == null || mAllMessagesAccount == null) {
createSpecialAccounts(); createSpecialAccounts();
} }
newAccounts = new ArrayList<BaseAccount>(accounts.length + newAccounts = new ArrayList<BaseAccount>(accounts.size() +
SPECIAL_ACCOUNTS_COUNT); SPECIAL_ACCOUNTS_COUNT);
newAccounts.add(mUnifiedInboxAccount); newAccounts.add(mUnifiedInboxAccount);
newAccounts.add(mAllMessagesAccount); newAccounts.add(mAllMessagesAccount);
} else { } else {
newAccounts = new ArrayList<BaseAccount>(accounts.length); newAccounts = new ArrayList<BaseAccount>(accounts.size());
} }
newAccounts.addAll(Arrays.asList(accounts)); newAccounts.addAll(accounts);
mAdapter = new AccountsAdapter(newAccounts.toArray(EMPTY_BASE_ACCOUNT_ARRAY)); mAdapter = new AccountsAdapter(newAccounts);
getListView().setAdapter(mAdapter); getListView().setAdapter(mAdapter);
if (!newAccounts.isEmpty()) { if (!newAccounts.isEmpty()) {
mHandler.progress(Window.PROGRESS_START); mHandler.progress(Window.PROGRESS_START);
@ -1735,7 +1736,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
} }
class AccountsAdapter extends ArrayAdapter<BaseAccount> { class AccountsAdapter extends ArrayAdapter<BaseAccount> {
public AccountsAdapter(BaseAccount[] accounts) { public AccountsAdapter(List<BaseAccount> accounts) {
super(Accounts.this, 0, accounts); super(Accounts.this, 0, accounts);
} }

View File

@ -261,7 +261,7 @@ public class ChooseFolder extends K9ListActivity {
mHandler.progress(false); mHandler.progress(false);
} }
@Override @Override
public void listFolders(Account account, Folder[] folders) { public void listFolders(Account account, List<? extends Folder> folders) {
if (!account.equals(mAccount)) { if (!account.equals(mAccount)) {
return; return;
} }

View File

@ -56,7 +56,7 @@ import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.LocalStore.LocalFolder; import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.search.LocalSearch; import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification.Attribute; import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.Searchfield; import com.fsck.k9.search.SearchSpecification.Searchfield;
@ -660,7 +660,7 @@ public class FolderList extends K9ListActivity {
} }
class FolderListAdapter extends BaseAdapter implements Filterable { class FolderListAdapter extends BaseAdapter implements Filterable {
private ArrayList<FolderInfoHolder> mFolders = new ArrayList<FolderInfoHolder>(); private List<FolderInfoHolder> mFolders = new ArrayList<FolderInfoHolder>();
private List<FolderInfoHolder> mFilteredFolders = Collections.unmodifiableList(mFolders); private List<FolderInfoHolder> mFilteredFolders = Collections.unmodifiableList(mFolders);
private Filter mFilter = new FolderListFilter(); private Filter mFilter = new FolderListFilter();
@ -740,7 +740,7 @@ public class FolderList extends K9ListActivity {
} }
@Override @Override
public void listFolders(Account account, Folder[] folders) { public void listFolders(Account account, List<? extends Folder> folders) {
if (account.equals(mAccount)) { if (account.equals(mAccount)) {
List<FolderInfoHolder> newFolders = new LinkedList<FolderInfoHolder>(); List<FolderInfoHolder> newFolders = new LinkedList<FolderInfoHolder>();
@ -1176,7 +1176,7 @@ public class FolderList extends K9ListActivity {
Locale locale = Locale.getDefault(); Locale locale = Locale.getDefault();
if ((searchTerm == null) || (searchTerm.length() == 0)) { if ((searchTerm == null) || (searchTerm.length() == 0)) {
ArrayList<FolderInfoHolder> list = new ArrayList<FolderInfoHolder>(mFolders); List<FolderInfoHolder> list = new ArrayList<FolderInfoHolder>(mFolders);
results.values = list; results.values = list;
results.count = list.size(); results.count = list.size();
} else { } else {
@ -1184,7 +1184,7 @@ public class FolderList extends K9ListActivity {
final String[] words = searchTermString.split(" "); final String[] words = searchTermString.split(" ");
final int wordCount = words.length; final int wordCount = words.length;
final ArrayList<FolderInfoHolder> newValues = new ArrayList<FolderInfoHolder>(); final List<FolderInfoHolder> newValues = new ArrayList<FolderInfoHolder>();
for (final FolderInfoHolder value : mFolders) { for (final FolderInfoHolder value : mFolders) {
if (value.displayName == null) { if (value.displayName == null) {

View File

@ -29,7 +29,7 @@ public class FolderListFilter<T> extends Filter {
/** /**
* All folders. * All folders.
*/ */
private ArrayList<T> mOriginalValues = null; private List<T> mOriginalValues = null;
/** /**
* Create a filter for a list of folders. * Create a filter for a list of folders.
@ -62,7 +62,7 @@ public class FolderListFilter<T> extends Filter {
Locale locale = Locale.getDefault(); Locale locale = Locale.getDefault();
if ((searchTerm == null) || (searchTerm.length() == 0)) { if ((searchTerm == null) || (searchTerm.length() == 0)) {
ArrayList<T> list = new ArrayList<T>(mOriginalValues); List<T> list = new ArrayList<T>(mOriginalValues);
results.values = list; results.values = list;
results.count = list.size(); results.count = list.size();
} else { } else {
@ -70,9 +70,9 @@ public class FolderListFilter<T> extends Filter {
final String[] words = searchTermString.split(" "); final String[] words = searchTermString.split(" ");
final int wordCount = words.length; final int wordCount = words.length;
final ArrayList<T> values = mOriginalValues; final List<T> values = mOriginalValues;
final ArrayList<T> newValues = new ArrayList<T>(); final List<T> newValues = new ArrayList<T>();
for (final T value : values) { for (final T value : values) {
final String valueText = value.toString().toLowerCase(locale); final String valueText = value.toString().toLowerCase(locale);

View File

@ -5,9 +5,11 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -107,9 +109,9 @@ import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody; import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.internet.TextBodyBuilder; import com.fsck.k9.mail.internet.TextBodyBuilder;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBody; import com.fsck.k9.mail.store.local.LocalAttachmentBody;
import com.fsck.k9.mail.store.LocalStore.TempFileBody; import com.fsck.k9.mail.store.local.TempFileBody;
import com.fsck.k9.mail.store.LocalStore.TempFileMessageBody; import com.fsck.k9.mail.store.local.TempFileMessageBody;
import com.fsck.k9.view.MessageWebView; import com.fsck.k9.view.MessageWebView;
import org.apache.james.mime4j.codec.EncoderUtil; import org.apache.james.mime4j.codec.EncoderUtil;
@ -190,8 +192,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private static final int CONTACT_PICKER_CC2 = 8; private static final int CONTACT_PICKER_CC2 = 8;
private static final int CONTACT_PICKER_BCC2 = 9; private static final int CONTACT_PICKER_BCC2 = 9;
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[0];
private static final int REQUEST_CODE_SIGN_ENCRYPT = 12; private static final int REQUEST_CODE_SIGN_ENCRYPT = 12;
/** /**
@ -1012,7 +1012,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
addAttachment(stream, type); addAttachment(stream, type);
} }
} else { } else {
ArrayList<Parcelable> list = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); List<Parcelable> list = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
if (list != null) { if (list != null) {
for (Parcelable parcelable : list) { for (Parcelable parcelable : list) {
Uri stream = (Uri) parcelable; Uri stream = (Uri) parcelable;
@ -1056,7 +1056,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
} }
private boolean addRecipients(TextView view, List<String> recipients) { private boolean addRecipients(TextView view, List<String> recipients) {
if (recipients == null || recipients.size() == 0) { if (recipients == null || recipients.isEmpty()) {
return false; return false;
} }
@ -1198,7 +1198,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
"\" from saved instance state", e); "\" from saved instance state", e);
} }
ArrayList<Attachment> attachments = savedInstanceState.getParcelableArrayList(STATE_KEY_ATTACHMENTS); List<Attachment> attachments = savedInstanceState.getParcelableArrayList(STATE_KEY_ATTACHMENTS);
for (Attachment attachment : attachments) { for (Attachment attachment : attachments) {
addAttachmentView(attachment); addAttachmentView(attachment);
if (attachment.loaderId > mMaxLoaderId) { if (attachment.loaderId > mMaxLoaderId) {
@ -1807,7 +1807,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
String[] emailsArray = null; String[] emailsArray = null;
if (mEncryptCheckbox.isChecked()) { if (mEncryptCheckbox.isChecked()) {
// get emails as array // get emails as array
ArrayList<String> emails = new ArrayList<String>(); List<String> emails = new ArrayList<String>();
for (Address address : getRecipientAddresses()) { for (Address address : getRecipientAddresses()) {
emails.add(address.getAddress()); emails.add(address.getAddress());
@ -1890,13 +1890,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private InputStream getOpenPgpInputStream() { private InputStream getOpenPgpInputStream() {
String text = buildText(false).getText(); String text = buildText(false).getText();
InputStream is = null; return new ByteArrayInputStream(text.getBytes(Charset.forName("UTF-8")));
try {
is = new ByteArrayInputStream(text.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
Log.e(K9.LOG_TAG, "UnsupportedEncodingException.", e);
}
return is;
} }
private void executeOpenPgpMethod(Intent intent) { private void executeOpenPgpMethod(Intent intent) {
@ -3921,7 +3915,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
List<Object> items = new ArrayList<Object>(); List<Object> items = new ArrayList<Object>();
Preferences prefs = Preferences.getPreferences(context.getApplicationContext()); Preferences prefs = Preferences.getPreferences(context.getApplicationContext());
Account[] accounts = prefs.getAvailableAccounts().toArray(EMPTY_ACCOUNT_ARRAY); Collection<Account> accounts = prefs.getAvailableAccounts();
for (Account account : accounts) { for (Account account : accounts) {
items.add(account); items.add(account);
List<Identity> identities = account.getIdentities(); List<Identity> identities = account.getIdentities();

View File

@ -451,10 +451,10 @@ public class MessageList extends K9FragmentActivity implements MessageListFragme
String[] accountUuids = mSearch.getAccountUuids(); String[] accountUuids = mSearch.getAccountUuids();
if (mSearch.searchAllAccounts()) { if (mSearch.searchAllAccounts()) {
Account[] accounts = prefs.getAccounts(); List<Account> accounts = prefs.getAccounts();
mSingleAccountMode = (accounts.length == 1); mSingleAccountMode = (accounts.size() == 1);
if (mSingleAccountMode) { if (mSingleAccountMode) {
mAccount = accounts[0]; mAccount = accounts.get(0);
} }
} else { } else {
mSingleAccountMode = (accountUuids.length == 1); mSingleAccountMode = (accountUuids.length == 1);

View File

@ -1,6 +1,8 @@
package com.fsck.k9.activity; package com.fsck.k9.activity;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
@ -14,6 +16,7 @@ import com.fsck.k9.Account;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.Preferences; import com.fsck.k9.Preferences;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.service.NotificationActionService; import com.fsck.k9.service.NotificationActionService;
public class NotificationDeleteConfirmation extends Activity { public class NotificationDeleteConfirmation extends Activity {
@ -25,7 +28,7 @@ public class NotificationDeleteConfirmation extends Activity {
private Account mAccount; private Account mAccount;
private ArrayList<MessageReference> mMessageRefs; private ArrayList<MessageReference> mMessageRefs;
public static PendingIntent getIntent(Context context, final Account account, final ArrayList<MessageReference> refs) { public static PendingIntent getIntent(Context context, final Account account, final Serializable refs) {
Intent i = new Intent(context, NotificationDeleteConfirmation.class); Intent i = new Intent(context, NotificationDeleteConfirmation.class);
i.putExtra(EXTRA_ACCOUNT, account.getUuid()); i.putExtra(EXTRA_ACCOUNT, account.getUuid());
i.putExtra(EXTRA_MESSAGE_LIST, refs); i.putExtra(EXTRA_MESSAGE_LIST, refs);

View File

@ -30,7 +30,7 @@ import android.widget.TextView;
* <li>{@link #actionUpgradeDatabases(Context, Intent)} will call {@link K9#areDatabasesUpToDate()} * <li>{@link #actionUpgradeDatabases(Context, Intent)} will call {@link K9#areDatabasesUpToDate()}
* to check if we already know whether the databases have been upgraded.</li> * to check if we already know whether the databases have been upgraded.</li>
* <li>{@link K9#areDatabasesUpToDate()} will compare the last known database version stored in a * <li>{@link K9#areDatabasesUpToDate()} will compare the last known database version stored in a
* {@link SharedPreferences} file to {@link com.fsck.k9.mail.store.LocalStore#DB_VERSION}. This * {@link SharedPreferences} file to {@link com.fsck.k9.mail.store.local.LocalStore#DB_VERSION}. This
* is done as an optimization because it's faster than opening all of the accounts' databases * is done as an optimization because it's faster than opening all of the accounts' databases
* one by one.</li> * one by one.</li>
* <li>If there was an error reading the cached database version or if it shows the databases need * <li>If there was an error reading the cached database version or if it shows the databases need

View File

@ -41,7 +41,7 @@ import com.fsck.k9.activity.ManageIdentities;
import com.fsck.k9.crypto.Apg; import com.fsck.k9.crypto.Apg;
import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Store; import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.LocalStore.LocalFolder; import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.service.MailService; import com.fsck.k9.service.MailService;

View File

@ -3,10 +3,8 @@ package com.fsck.k9.activity.setup;
import java.io.Serializable; import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Locale; import java.util.Locale;
import android.app.AlertDialog; import android.app.AlertDialog;
@ -35,6 +33,7 @@ import com.fsck.k9.Preferences;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.activity.K9Activity; import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection; import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.AuthType; import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ConnectionSecurity; import com.fsck.k9.mail.ConnectionSecurity;
@ -281,8 +280,8 @@ public class AccountSetupBasics extends K9Activity
URI incomingUri = null; URI incomingUri = null;
URI outgoingUri = null; URI outgoingUri = null;
try { try {
String userEnc = URLEncoder.encode(user, "UTF-8"); String userEnc = UrlEncodingHelper.encodeUtf8(user);
String passwordEnc = URLEncoder.encode(password, "UTF-8"); String passwordEnc = UrlEncodingHelper.encodeUtf8(password);
String incomingUsername = mProvider.incomingUsernameTemplate; String incomingUsername = mProvider.incomingUsernameTemplate;
incomingUsername = incomingUsername.replaceAll("\\$email", email); incomingUsername = incomingUsername.replaceAll("\\$email", email);
@ -338,9 +337,6 @@ public class AccountSetupBasics extends K9Activity
} }
// Check incoming here. Then check outgoing in onActivityResult() // Check incoming here. Then check outgoing in onActivityResult()
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.INCOMING); AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.INCOMING);
} catch (UnsupportedEncodingException enc) {
// This really shouldn't happen since the encoding is hardcoded to UTF-8
Log.e(K9.LOG_TAG, "Couldn't urlencode username or password.", enc);
} catch (URISyntaxException use) { } catch (URISyntaxException use) {
/* /*
* If there is some problem with the URI we give up and go on to * If there is some problem with the URI we give up and go on to

View File

@ -16,8 +16,8 @@ import com.fsck.k9.mail.Folder.FolderClass;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store; import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalFolder; import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.service.MailService; import com.fsck.k9.service.MailService;
public class FolderSettings extends K9PreferenceActivity { public class FolderSettings extends K9PreferenceActivity {

View File

@ -11,8 +11,8 @@ import android.support.v4.content.LocalBroadcastManager;
import com.fsck.k9.fragment.MessageListFragment; import com.fsck.k9.fragment.MessageListFragment;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.LocalStore.LocalFolder; import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalMessage; import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.provider.EmailProvider; import com.fsck.k9.provider.EmailProvider;
/** /**
@ -42,9 +42,9 @@ public class EmailProviderCache {
private String mAccountUuid; private String mAccountUuid;
private Map<Long, Map<String, String>> mMessageCache = new HashMap<Long, Map<String, String>>(); private final Map<Long, Map<String, String>> mMessageCache = new HashMap<Long, Map<String, String>>();
private Map<Long, Map<String, String>> mThreadCache = new HashMap<Long, Map<String, String>>(); private final Map<Long, Map<String, String>> mThreadCache = new HashMap<Long, Map<String, String>>();
private Map<Long, Long> mHiddenMessageCache = new HashMap<Long, Long>(); private final Map<Long, Long> mHiddenMessageCache = new HashMap<Long, Long>();
private EmailProviderCache(String accountUuid) { private EmailProviderCache(String accountUuid) {
@ -101,7 +101,7 @@ public class EmailProviderCache {
Map<String, String> map = mMessageCache.get(messageId); Map<String, String> map = mMessageCache.get(messageId);
if (map != null) { if (map != null) {
map.remove(columnName); map.remove(columnName);
if (map.size() == 0) { if (map.isEmpty()) {
mMessageCache.remove(messageId); mMessageCache.remove(messageId);
} }
} }
@ -115,7 +115,7 @@ public class EmailProviderCache {
Map<String, String> map = mThreadCache.get(threadRootId); Map<String, String> map = mThreadCache.get(threadRootId);
if (map != null) { if (map != null) {
map.remove(columnName); map.remove(columnName);
if (map.size() == 0) { if (map.isEmpty()) {
mThreadCache.remove(threadRootId); mThreadCache.remove(threadRootId);
} }
} }
@ -143,7 +143,7 @@ public class EmailProviderCache {
} }
} }
public void unhideMessages(Message[] messages) { public void unhideMessages(List<? extends Message> messages) {
synchronized (mHiddenMessageCache) { synchronized (mHiddenMessageCache) {
for (Message message : messages) { for (Message message : messages) {
LocalMessage localMessage = (LocalMessage) message; LocalMessage localMessage = (LocalMessage) message;

View File

@ -108,7 +108,7 @@ public class EmailProviderCacheCursor extends CursorWrapper {
@Override @Override
public boolean moveToPosition(int position) { public boolean moveToPosition(int position) {
if (mHiddenRows.size() == 0) { if (mHiddenRows.isEmpty()) {
return super.moveToPosition(position); return super.moveToPosition(position);
} }
@ -126,7 +126,7 @@ public class EmailProviderCacheCursor extends CursorWrapper {
@Override @Override
public int getPosition() { public int getPosition() {
if (mHiddenRows.size() == 0) { if (mHiddenRows.isEmpty()) {
return super.getPosition(); return super.getPosition();
} }

View File

@ -2,12 +2,14 @@ package com.fsck.k9.controller;
import java.io.CharArrayWriter; import java.io.CharArrayWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -79,10 +81,10 @@ import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.internet.MimeMessage; import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody; import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalFolder; import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.mail.store.LocalStore.LocalMessage; import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.store.LocalStore.PendingCommand; import com.fsck.k9.mail.store.local.LocalStore.PendingCommand;
import com.fsck.k9.mail.store.Pop3Store; import com.fsck.k9.mail.store.Pop3Store;
import com.fsck.k9.mail.store.UnavailableAccountException; import com.fsck.k9.mail.store.UnavailableAccountException;
import com.fsck.k9.mail.store.UnavailableStorageException; import com.fsck.k9.mail.store.UnavailableStorageException;
@ -116,16 +118,6 @@ public class MessagingController implements Runnable {
*/ */
private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* Immutable empty {@link Message} array
*/
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
/**
* Immutable empty {@link Folder} array
*/
private static final Folder[] EMPTY_FOLDER_ARRAY = new Folder[0];
/** /**
* The maximum message size that we'll consider to be "small". A small message is downloaded * The maximum message size that we'll consider to be "small". A small message is downloaded
* in full immediately instead of in pieces. Anything over this size will be downloaded in * in full immediately instead of in pieces. Anything over this size will be downloaded in
@ -295,17 +287,14 @@ public class MessagingController implements Runnable {
} }
/** /**
* Gets a list of references for all pending messages for the notification. * Adds a list of references for all pending messages for the notification to the supplied
* * List.
* @return Message reference list
*/ */
public ArrayList<MessageReference> getAllMessageRefs() { public void supplyAllMessageRefs(List<MessageReference> refs) {
ArrayList<MessageReference> refs = new ArrayList<MessageReference>();
for (Message m : messages) { for (Message m : messages) {
refs.add(m.makeMessageReference()); refs.add(m.makeMessageReference());
} }
refs.addAll(droppedMessages); refs.addAll(droppedMessages);
return refs;
} }
/** /**
@ -319,10 +308,9 @@ public class MessagingController implements Runnable {
}; };
// Key is accountNumber // Key is accountNumber
private ConcurrentHashMap<Integer, NotificationData> notificationData = new ConcurrentHashMap<Integer, NotificationData>(); private final ConcurrentMap<Integer, NotificationData> notificationData = new ConcurrentHashMap<Integer, NotificationData>();
private static final Flag[] SYNC_FLAGS = new Flag[] { Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED };
private static final Set<Flag> SYNC_FLAGS = EnumSet.of(Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED);
private void suppressMessages(Account account, List<Message> messages) { private void suppressMessages(Account account, List<Message> messages) {
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(), EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
@ -330,7 +318,7 @@ public class MessagingController implements Runnable {
cache.hideMessages(messages); cache.hideMessages(messages);
} }
private void unsuppressMessages(Account account, Message[] messages) { private void unsuppressMessages(Account account, List<? extends Message> messages) {
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(), EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
mApplication.getApplicationContext()); mApplication.getApplicationContext());
cache.unhideMessages(messages); cache.unhideMessages(messages);
@ -574,15 +562,13 @@ public class MessagingController implements Runnable {
Store localStore = account.getLocalStore(); Store localStore = account.getLocalStore();
localFolders = localStore.getPersonalNamespaces(false); localFolders = localStore.getPersonalNamespaces(false);
Folder[] folderArray = localFolders.toArray(EMPTY_FOLDER_ARRAY);
if (refreshRemote || localFolders.isEmpty()) { if (refreshRemote || localFolders.isEmpty()) {
doRefreshRemote(account, listener); doRefreshRemote(account, listener);
return; return;
} }
for (MessagingListener l : getListeners(listener)) { for (MessagingListener l : getListeners(listener)) {
l.listFolders(account, folderArray); l.listFolders(account, localFolders);
} }
} catch (Exception e) { } catch (Exception e) {
for (MessagingListener l : getListeners(listener)) { for (MessagingListener l : getListeners(listener)) {
@ -654,10 +640,9 @@ public class MessagingController implements Runnable {
} }
localFolders = localStore.getPersonalNamespaces(false); localFolders = localStore.getPersonalNamespaces(false);
Folder[] folderArray = localFolders.toArray(EMPTY_FOLDER_ARRAY);
for (MessagingListener l : getListeners(listener)) { for (MessagingListener l : getListeners(listener)) {
l.listFolders(account, folderArray); l.listFolders(account, localFolders);
} }
for (MessagingListener l : getListeners(listener)) { for (MessagingListener l : getListeners(listener)) {
l.listFoldersFinished(account); l.listFoldersFinished(account);
@ -694,7 +679,7 @@ public class MessagingController implements Runnable {
public void searchLocalMessagesSynchronous(final LocalSearch search, final MessagingListener listener) { public void searchLocalMessagesSynchronous(final LocalSearch search, final MessagingListener listener) {
final AccountStats stats = new AccountStats(); final AccountStats stats = new AccountStats();
final Set<String> uuidSet = new HashSet<String>(Arrays.asList(search.getAccountUuids())); final Set<String> uuidSet = new HashSet<String>(Arrays.asList(search.getAccountUuids()));
Account[] accounts = Preferences.getPreferences(mApplication.getApplicationContext()).getAccounts(); List<Account> accounts = Preferences.getPreferences(mApplication.getApplicationContext()).getAccounts();
boolean allAccounts = uuidSet.contains(SearchSpecification.ALL_ACCOUNTS); boolean allAccounts = uuidSet.contains(SearchSpecification.ALL_ACCOUNTS);
// for every account we want to search do the query in the localstore // for every account we want to search do the query in the localstore
@ -755,7 +740,7 @@ public class MessagingController implements Runnable {
public Future<?> searchRemoteMessages(final String acctUuid, final String folderName, final String query, public Future<?> searchRemoteMessages(final String acctUuid, final String folderName, final String query,
final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) { final Set<Flag> requiredFlags, final Set<Flag> forbiddenFlags, final MessagingListener listener) {
if (K9.DEBUG) { if (K9.DEBUG) {
String msg = "searchRemoteMessages (" String msg = "searchRemoteMessages ("
+ "acct=" + acctUuid + "acct=" + acctUuid
@ -773,7 +758,7 @@ public class MessagingController implements Runnable {
}); });
} }
public void searchRemoteMessagesSynchronous(final String acctUuid, final String folderName, final String query, public void searchRemoteMessagesSynchronous(final String acctUuid, final String folderName, final String query,
final Flag[] requiredFlags, final Flag[] forbiddenFlags, final MessagingListener listener) { final Set<Flag> requiredFlags, final Set<Flag> forbiddenFlags, final MessagingListener listener) {
final Account acct = Preferences.getPreferences(mApplication.getApplicationContext()).getAccount(acctUuid); final Account acct = Preferences.getPreferences(mApplication.getApplicationContext()).getAccount(acctUuid);
if (listener != null) { if (listener != null) {
@ -885,10 +870,10 @@ public class MessagingController implements Runnable {
LocalMessage localMsg = localFolder.getMessage(message.getUid()); LocalMessage localMsg = localFolder.getMessage(message.getUid());
if (localMsg == null) { if (localMsg == null) {
remoteFolder.fetch(new Message [] {message}, header, null); remoteFolder.fetch(Collections.singletonList(message), header, null);
//fun fact: ImapFolder.fetch can't handle getting STRUCTURE at same time as headers //fun fact: ImapFolder.fetch can't handle getting STRUCTURE at same time as headers
remoteFolder.fetch(new Message [] {message}, structure, null); remoteFolder.fetch(Collections.singletonList(message), structure, null);
localFolder.appendMessages(new Message [] {message}); localFolder.appendMessages(Collections.singletonList(message));
localMsg = localFolder.getMessage(message.getUid()); localMsg = localFolder.getMessage(message.getUid());
} }
@ -992,8 +977,8 @@ public class MessagingController implements Runnable {
final LocalFolder localFolder = tLocalFolder; final LocalFolder localFolder = tLocalFolder;
localFolder.open(Folder.OPEN_MODE_RW); localFolder.open(Folder.OPEN_MODE_RW);
localFolder.updateLastUid(); localFolder.updateLastUid();
Message[] localMessages = localFolder.getMessages(null); List<? extends Message> localMessages = localFolder.getMessages(null);
HashMap<String, Message> localUidMap = new HashMap<String, Message>(); Map<String, Message> localUidMap = new HashMap<String, Message>();
for (Message message : localMessages) { for (Message message : localMessages) {
localUidMap.put(message.getUid(), message); localUidMap.put(message.getUid(), message);
} }
@ -1058,9 +1043,8 @@ public class MessagingController implements Runnable {
visibleLimit = K9.DEFAULT_VISIBLE_LIMIT; visibleLimit = K9.DEFAULT_VISIBLE_LIMIT;
} }
Message[] remoteMessageArray = EMPTY_MESSAGE_ARRAY; final List<Message> remoteMessages = new ArrayList<Message>();
final ArrayList<Message> remoteMessages = new ArrayList<Message>(); Map<String, Message> remoteUidMap = new HashMap<String, Message>();
HashMap<String, Message> remoteUidMap = new HashMap<String, Message>();
if (K9.DEBUG) if (K9.DEBUG)
Log.v(K9.LOG_TAG, "SYNC: Remote message count for folder " + folder + " is " + remoteMessageCount); Log.v(K9.LOG_TAG, "SYNC: Remote message count for folder " + folder + " is " + remoteMessageCount);
@ -1086,9 +1070,9 @@ public class MessagingController implements Runnable {
} }
remoteMessageArray = remoteFolder.getMessages(remoteStart, remoteEnd, earliestDate, null); List<? extends Message> remoteMessageArray = remoteFolder.getMessages(remoteStart, remoteEnd, earliestDate, null);
int messageCount = remoteMessageArray.length; int messageCount = remoteMessageArray.size();
for (Message thisMess : remoteMessageArray) { for (Message thisMess : remoteMessageArray) {
headerProgress.incrementAndGet(); headerProgress.incrementAndGet();
@ -1104,7 +1088,6 @@ public class MessagingController implements Runnable {
if (K9.DEBUG) if (K9.DEBUG)
Log.v(K9.LOG_TAG, "SYNC: Got " + remoteUidMap.size() + " messages for folder " + folder); Log.v(K9.LOG_TAG, "SYNC: Got " + remoteUidMap.size() + " messages for folder " + folder);
remoteMessageArray = null;
for (MessagingListener l : getListeners(listener)) { for (MessagingListener l : getListeners(listener)) {
l.synchronizeMailboxHeadersFinished(account, folder, headerProgress.get(), remoteUidMap.size()); l.synchronizeMailboxHeadersFinished(account, folder, headerProgress.get(), remoteUidMap.size());
} }
@ -1117,7 +1100,7 @@ public class MessagingController implements Runnable {
* Remove any messages that are in the local store but no longer on the remote store or are too old * Remove any messages that are in the local store but no longer on the remote store or are too old
*/ */
if (account.syncRemoteDeletions()) { if (account.syncRemoteDeletions()) {
ArrayList<Message> destroyMessages = new ArrayList<Message>(); List<Message> destroyMessages = new ArrayList<Message>();
for (Message localMessage : localMessages) { for (Message localMessage : localMessages) {
if (remoteUidMap.get(localMessage.getUid()) == null) { if (remoteUidMap.get(localMessage.getUid()) == null) {
destroyMessages.add(localMessage); destroyMessages.add(localMessage);
@ -1125,7 +1108,7 @@ public class MessagingController implements Runnable {
} }
localFolder.destroyMessages(destroyMessages.toArray(EMPTY_MESSAGE_ARRAY)); localFolder.destroyMessages(destroyMessages);
for (Message destroyMessage : destroyMessages) { for (Message destroyMessage : destroyMessages) {
for (MessagingListener l : getListeners(listener)) { for (MessagingListener l : getListeners(listener)) {
@ -1280,7 +1263,7 @@ public class MessagingController implements Runnable {
Log.e(K9.LOG_TAG, "Unable to getUnreadMessageCount for account: " + account, e); Log.e(K9.LOG_TAG, "Unable to getUnreadMessageCount for account: " + account, e);
} }
ArrayList<Message> syncFlagMessages = new ArrayList<Message>(); List<Message> syncFlagMessages = new ArrayList<Message>();
List<Message> unsyncedMessages = new ArrayList<Message>(); List<Message> unsyncedMessages = new ArrayList<Message>();
final AtomicInteger newMessages = new AtomicInteger(0); final AtomicInteger newMessages = new AtomicInteger(0);
@ -1300,8 +1283,8 @@ public class MessagingController implements Runnable {
Log.d(K9.LOG_TAG, "SYNC: Have " + unsyncedMessages.size() + " unsynced messages"); Log.d(K9.LOG_TAG, "SYNC: Have " + unsyncedMessages.size() + " unsynced messages");
messages.clear(); messages.clear();
final ArrayList<Message> largeMessages = new ArrayList<Message>(); final List<Message> largeMessages = new ArrayList<Message>();
final ArrayList<Message> smallMessages = new ArrayList<Message>(); final List<Message> smallMessages = new ArrayList<Message>();
if (!unsyncedMessages.isEmpty()) { if (!unsyncedMessages.isEmpty()) {
/* /*
@ -1416,7 +1399,7 @@ public class MessagingController implements Runnable {
final Folder remoteFolder, final Folder remoteFolder,
final Account account, final Account account,
final List<Message> unsyncedMessages, final List<Message> unsyncedMessages,
final ArrayList<Message> syncFlagMessages, final List<Message> syncFlagMessages,
boolean flagSyncOnly) throws MessagingException { boolean flagSyncOnly) throws MessagingException {
if (message.isSet(Flag.DELETED)) { if (message.isSet(Flag.DELETED)) {
syncFlagMessages.add(message); syncFlagMessages.add(message);
@ -1437,7 +1420,7 @@ public class MessagingController implements Runnable {
Log.v(K9.LOG_TAG, "Message with uid " + message.getUid() + " is partially or fully downloaded"); Log.v(K9.LOG_TAG, "Message with uid " + message.getUid() + " is partially or fully downloaded");
// Store the updated message locally // Store the updated message locally
localFolder.appendMessages(new Message[] { message }); localFolder.appendMessages(Collections.singletonList(message));
localMessage = localFolder.getMessage(message.getUid()); localMessage = localFolder.getMessage(message.getUid());
@ -1475,8 +1458,8 @@ public class MessagingController implements Runnable {
private void fetchUnsyncedMessages(final Account account, final Folder remoteFolder, private void fetchUnsyncedMessages(final Account account, final Folder remoteFolder,
final LocalFolder localFolder, final LocalFolder localFolder,
List<Message> unsyncedMessages, List<Message> unsyncedMessages,
final ArrayList<Message> smallMessages, final List<Message> smallMessages,
final ArrayList<Message> largeMessages, final List<Message> largeMessages,
final AtomicInteger progress, final AtomicInteger progress,
final int todo, final int todo,
FetchProfile fp) throws MessagingException { FetchProfile fp) throws MessagingException {
@ -1489,7 +1472,7 @@ public class MessagingController implements Runnable {
*/ */
final List<Message> chunk = new ArrayList<Message>(UNSYNC_CHUNK_SIZE); final List<Message> chunk = new ArrayList<Message>(UNSYNC_CHUNK_SIZE);
remoteFolder.fetch(unsyncedMessages.toArray(EMPTY_MESSAGE_ARRAY), fp, remoteFolder.fetch(unsyncedMessages, fp,
new MessageRetrievalListener() { new MessageRetrievalListener() {
@Override @Override
public void messageFinished(Message message, int number, int ofTotal) { public void messageFinished(Message message, int number, int ofTotal) {
@ -1578,7 +1561,7 @@ public class MessagingController implements Runnable {
} }
try { try {
// Store the new message locally // Store the new message locally
localFolder.appendMessages(messages.toArray(new Message[messages.size()])); localFolder.appendMessages(messages);
for (final Message message : messages) { for (final Message message : messages) {
final Message localMessage = localFolder.getMessage(message.getUid()); final Message localMessage = localFolder.getMessage(message.getUid());
@ -1611,7 +1594,7 @@ public class MessagingController implements Runnable {
private void downloadSmallMessages(final Account account, final Folder remoteFolder, private void downloadSmallMessages(final Account account, final Folder remoteFolder,
final LocalFolder localFolder, final LocalFolder localFolder,
ArrayList<Message> smallMessages, List<Message> smallMessages,
final AtomicInteger progress, final AtomicInteger progress,
final int unreadBeforeStart, final int unreadBeforeStart,
final AtomicInteger newMessages, final AtomicInteger newMessages,
@ -1624,7 +1607,7 @@ public class MessagingController implements Runnable {
if (K9.DEBUG) if (K9.DEBUG)
Log.d(K9.LOG_TAG, "SYNC: Fetching small messages for folder " + folder); Log.d(K9.LOG_TAG, "SYNC: Fetching small messages for folder " + folder);
remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]), remoteFolder.fetch(smallMessages,
fp, new MessageRetrievalListener() { fp, new MessageRetrievalListener() {
@Override @Override
public void messageFinished(final Message message, int number, int ofTotal) { public void messageFinished(final Message message, int number, int ofTotal) {
@ -1690,7 +1673,7 @@ public class MessagingController implements Runnable {
private void downloadLargeMessages(final Account account, final Folder remoteFolder, private void downloadLargeMessages(final Account account, final Folder remoteFolder,
final LocalFolder localFolder, final LocalFolder localFolder,
ArrayList<Message> largeMessages, List<Message> largeMessages,
final AtomicInteger progress, final AtomicInteger progress,
final int unreadBeforeStart, final int unreadBeforeStart,
final AtomicInteger newMessages, final AtomicInteger newMessages,
@ -1703,7 +1686,7 @@ public class MessagingController implements Runnable {
if (K9.DEBUG) if (K9.DEBUG)
Log.d(K9.LOG_TAG, "SYNC: Fetching large messages for folder " + folder); Log.d(K9.LOG_TAG, "SYNC: Fetching large messages for folder " + folder);
remoteFolder.fetch(largeMessages.toArray(new Message[largeMessages.size()]), fp, null); remoteFolder.fetch(largeMessages, fp, null);
for (Message message : largeMessages) { for (Message message : largeMessages) {
if (!shouldImportMessage(account, folder, message, progress, earliestDate)) { if (!shouldImportMessage(account, folder, message, progress, earliestDate)) {
@ -1726,10 +1709,10 @@ public class MessagingController implements Runnable {
* they equal we can mark this SYNCHRONIZED instead of PARTIALLY_SYNCHRONIZED * they equal we can mark this SYNCHRONIZED instead of PARTIALLY_SYNCHRONIZED
*/ */
remoteFolder.fetch(new Message[] { message }, fp, null); remoteFolder.fetch(Collections.singletonList(message), fp, null);
// Store the updated message locally // Store the updated message locally
localFolder.appendMessages(new Message[] { message }); localFolder.appendMessages(Collections.singletonList(message));
Message localMessage = localFolder.getMessage(message.getUid()); Message localMessage = localFolder.getMessage(message.getUid());
@ -1770,7 +1753,7 @@ public class MessagingController implements Runnable {
remoteFolder.fetchPart(message, part, null); remoteFolder.fetchPart(message, part, null);
} }
// Store the updated message locally // Store the updated message locally
localFolder.appendMessages(new Message[] { message }); localFolder.appendMessages(Collections.singletonList(message));
Message localMessage = localFolder.getMessage(message.getUid()); Message localMessage = localFolder.getMessage(message.getUid());
@ -1815,7 +1798,7 @@ public class MessagingController implements Runnable {
private void refreshLocalMessageFlags(final Account account, final Folder remoteFolder, private void refreshLocalMessageFlags(final Account account, final Folder remoteFolder,
final LocalFolder localFolder, final LocalFolder localFolder,
ArrayList<Message> syncFlagMessages, List<Message> syncFlagMessages,
final AtomicInteger progress, final AtomicInteger progress,
final int todo final int todo
) throws MessagingException { ) throws MessagingException {
@ -1836,7 +1819,7 @@ public class MessagingController implements Runnable {
} }
} }
remoteFolder.fetch(undeletedMessages.toArray(EMPTY_MESSAGE_ARRAY), fp, null); remoteFolder.fetch(undeletedMessages, fp, null);
for (Message remoteMessage : syncFlagMessages) { for (Message remoteMessage : syncFlagMessages) {
Message localMessage = localFolder.getMessage(remoteMessage.getUid()); Message localMessage = localFolder.getMessage(remoteMessage.getUid());
boolean messageChanged = syncFlags(localMessage, remoteMessage); boolean messageChanged = syncFlags(localMessage, remoteMessage);
@ -1952,7 +1935,7 @@ public class MessagingController implements Runnable {
private void processPendingCommandsSynchronous(Account account) throws MessagingException { private void processPendingCommandsSynchronous(Account account) throws MessagingException {
LocalStore localStore = account.getLocalStore(); LocalStore localStore = account.getLocalStore();
ArrayList<PendingCommand> commands = localStore.getPendingCommands(); List<PendingCommand> commands = localStore.getPendingCommands();
int progress = 0; int progress = 0;
int todo = commands.size(); int todo = commands.size();
@ -2111,10 +2094,10 @@ public class MessagingController implements Runnable {
*/ */
FetchProfile fp = new FetchProfile(); FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.BODY); fp.add(FetchProfile.Item.BODY);
localFolder.fetch(new Message[] { localMessage } , fp, null); localFolder.fetch(Collections.singletonList(localMessage) , fp, null);
String oldUid = localMessage.getUid(); String oldUid = localMessage.getUid();
localMessage.setFlag(Flag.X_REMOTE_COPY_STARTED, true); localMessage.setFlag(Flag.X_REMOTE_COPY_STARTED, true);
remoteFolder.appendMessages(new Message[] { localMessage }); remoteFolder.appendMessages(Collections.singletonList(localMessage));
localFolder.changeUid(localMessage); localFolder.changeUid(localMessage);
for (MessagingListener l : getListeners()) { for (MessagingListener l : getListeners()) {
@ -2129,7 +2112,7 @@ public class MessagingController implements Runnable {
*/ */
FetchProfile fp = new FetchProfile(); FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.ENVELOPE);
remoteFolder.fetch(new Message[] { remoteMessage }, fp, null); remoteFolder.fetch(Collections.singletonList(remoteMessage), fp, null);
Date localDate = localMessage.getInternalDate(); Date localDate = localMessage.getInternalDate();
Date remoteDate = remoteMessage.getInternalDate(); Date remoteDate = remoteMessage.getInternalDate();
if (remoteDate != null && remoteDate.compareTo(localDate) > 0) { if (remoteDate != null && remoteDate.compareTo(localDate) > 0) {
@ -2146,12 +2129,12 @@ public class MessagingController implements Runnable {
fp.clear(); fp.clear();
fp = new FetchProfile(); fp = new FetchProfile();
fp.add(FetchProfile.Item.BODY); fp.add(FetchProfile.Item.BODY);
localFolder.fetch(new Message[] { localMessage }, fp, null); localFolder.fetch(Collections.singletonList(localMessage), fp, null);
String oldUid = localMessage.getUid(); String oldUid = localMessage.getUid();
localMessage.setFlag(Flag.X_REMOTE_COPY_STARTED, true); localMessage.setFlag(Flag.X_REMOTE_COPY_STARTED, true);
remoteFolder.appendMessages(new Message[] { localMessage }); remoteFolder.appendMessages(Collections.singletonList(localMessage));
localFolder.changeUid(localMessage); localFolder.changeUid(localMessage);
for (MessagingListener l : getListeners()) { for (MessagingListener l : getListeners()) {
l.messageUidChanged(account, folder, oldUid, localMessage.getUid()); l.messageUidChanged(account, folder, oldUid, localMessage.getUid());
@ -2325,14 +2308,14 @@ public class MessagingController implements Runnable {
if (K9.FOLDER_NONE.equals(destFolderName)) { if (K9.FOLDER_NONE.equals(destFolderName)) {
destFolderName = null; destFolderName = null;
} }
remoteSrcFolder.delete(messages.toArray(EMPTY_MESSAGE_ARRAY), destFolderName); remoteSrcFolder.delete(messages, destFolderName);
} else { } else {
remoteDestFolder = remoteStore.getFolder(destFolder); remoteDestFolder = remoteStore.getFolder(destFolder);
if (isCopy) { if (isCopy) {
remoteUidMap = remoteSrcFolder.copyMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder); remoteUidMap = remoteSrcFolder.copyMessages(messages, remoteDestFolder);
} else { } else {
remoteUidMap = remoteSrcFolder.moveMessages(messages.toArray(EMPTY_MESSAGE_ARRAY), remoteDestFolder); remoteUidMap = remoteSrcFolder.moveMessages(messages, remoteDestFolder);
} }
} }
if (!isCopy && Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) { if (!isCopy && Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) {
@ -2425,7 +2408,7 @@ public class MessagingController implements Runnable {
if (messages.isEmpty()) { if (messages.isEmpty()) {
return; return;
} }
remoteFolder.setFlags(messages.toArray(EMPTY_MESSAGE_ARRAY), new Flag[] { flag }, newState); remoteFolder.setFlags(messages, Collections.singleton(flag), newState);
} finally { } finally {
closeFolder(remoteFolder); closeFolder(remoteFolder);
} }
@ -2571,9 +2554,9 @@ public class MessagingController implements Runnable {
} }
if (isCopy) { if (isCopy) {
remoteSrcFolder.copyMessages(new Message[] { remoteMessage }, remoteDestFolder); remoteSrcFolder.copyMessages(Collections.singletonList(remoteMessage), remoteDestFolder);
} else { } else {
remoteSrcFolder.moveMessages(new Message[] { remoteMessage }, remoteDestFolder); remoteSrcFolder.moveMessages(Collections.singletonList(remoteMessage), remoteDestFolder);
} }
remoteSrcFolder.close(); remoteSrcFolder.close();
remoteDestFolder.close(); remoteDestFolder.close();
@ -2587,7 +2570,7 @@ public class MessagingController implements Runnable {
Store localStore = account.getLocalStore(); Store localStore = account.getLocalStore();
localFolder = (LocalFolder) localStore.getFolder(folder); localFolder = (LocalFolder) localStore.getFolder(folder);
localFolder.open(Folder.OPEN_MODE_RW); localFolder.open(Folder.OPEN_MODE_RW);
Message[] messages = localFolder.getMessages(null, false); List<? extends Message> messages = localFolder.getMessages(null, false);
for (Message message : messages) { for (Message message : messages) {
if (!message.isSet(Flag.SEEN)) { if (!message.isSet(Flag.SEEN)) {
message.setFlag(Flag.SEEN, true); message.setFlag(Flag.SEEN, true);
@ -2617,7 +2600,7 @@ public class MessagingController implements Runnable {
return; return;
} }
remoteFolder.setFlags(new Flag[] {Flag.SEEN}, true); remoteFolder.setFlags(Collections.singleton(Flag.SEEN), true);
remoteFolder.close(); remoteFolder.close();
} catch (UnsupportedOperationException uoe) { } catch (UnsupportedOperationException uoe) {
Log.w(K9.LOG_TAG, "Could not mark all server-side as read because store doesn't support operation", uoe); Log.w(K9.LOG_TAG, "Could not mark all server-side as read because store doesn't support operation", uoe);
@ -2727,10 +2710,8 @@ public class MessagingController implements Runnable {
Store localStore = account.getLocalStore(); Store localStore = account.getLocalStore();
LocalFolder localFolder = (LocalFolder)localStore.getFolder(account.getErrorFolderName()); LocalFolder localFolder = (LocalFolder)localStore.getFolder(account.getErrorFolderName());
Message[] messages = new Message[1];
MimeMessage message = new MimeMessage(); MimeMessage message = new MimeMessage();
message.setBody(new TextBody(body)); message.setBody(new TextBody(body));
message.setFlag(Flag.X_DOWNLOADED_FULL, true); message.setFlag(Flag.X_DOWNLOADED_FULL, true);
message.setSubject(subject); message.setSubject(subject);
@ -2740,9 +2721,8 @@ public class MessagingController implements Runnable {
message.setInternalDate(nowDate); message.setInternalDate(nowDate);
message.addSentDate(nowDate); message.addSentDate(nowDate);
message.setFrom(new Address(account.getEmail(), "K9mail internal")); message.setFrom(new Address(account.getEmail(), "K9mail internal"));
messages[0] = message;
localFolder.appendMessages(messages); localFolder.appendMessages(Collections.singletonList(message));
localFolder.clearMessagesOlderThan(nowTime - (15 * 60 * 1000)); localFolder.clearMessagesOlderThan(nowTime - (15 * 60 * 1000));
@ -2874,7 +2854,7 @@ public class MessagingController implements Runnable {
* @param newState * @param newState
* {@code true}, if the flag should be set. {@code false} if it should be removed. * {@code true}, if the flag should be set. {@code false} if it should be removed.
*/ */
public void setFlag(Account account, String folderName, Message[] messages, Flag flag, public void setFlag(Account account, String folderName, List<Message> messages, Flag flag,
boolean newState) { boolean newState) {
// TODO: Put this into the background, but right now some callers depend on the message // TODO: Put this into the background, but right now some callers depend on the message
// objects being modified right after this method returns. // objects being modified right after this method returns.
@ -2896,7 +2876,7 @@ public class MessagingController implements Runnable {
} }
// Update the messages in the local store // Update the messages in the local store
localFolder.setFlags(messages, new Flag[] {flag}, newState); localFolder.setFlags(messages, Collections.singleton(flag), newState);
int unreadMessageCount = localFolder.getUnreadMessageCount(); int unreadMessageCount = localFolder.getUnreadMessageCount();
for (MessagingListener l : getListeners()) { for (MessagingListener l : getListeners()) {
@ -2914,9 +2894,9 @@ public class MessagingController implements Runnable {
return; return;
} }
String[] uids = new String[messages.length]; String[] uids = new String[messages.size()];
for (int i = 0, end = uids.length; i < end; i++) { for (int i = 0, end = uids.length; i < end; i++) {
uids[i] = messages[i].getUid(); uids[i] = messages.get(i).getUid();
} }
queueSetFlag(account, folderName, Boolean.toString(newState), flag.toString(), uids); queueSetFlag(account, folderName, Boolean.toString(newState), flag.toString(), uids);
@ -2953,7 +2933,7 @@ public class MessagingController implements Runnable {
Message message = localFolder.getMessage(uid); Message message = localFolder.getMessage(uid);
if (message != null) { if (message != null) {
setFlag(account, folderName, new Message[] { message }, flag, newState); setFlag(account, folderName, Collections.singletonList(message), flag, newState);
} }
} catch (MessagingException me) { } catch (MessagingException me) {
addErrorMessage(account, null, me); addErrorMessage(account, null, me);
@ -3026,7 +3006,7 @@ public class MessagingController implements Runnable {
FetchProfile fp = new FetchProfile(); FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.ENVELOPE);
fp.add(FetchProfile.Item.BODY); fp.add(FetchProfile.Item.BODY);
localFolder.fetch(new Message[] { message }, fp, null); localFolder.fetch(Collections.singletonList(message), fp, null);
} else { } else {
/* /*
* At this point the message is not available, so we need to download it * At this point the message is not available, so we need to download it
@ -3042,16 +3022,16 @@ public class MessagingController implements Runnable {
FetchProfile fp = new FetchProfile(); FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.BODY); fp.add(FetchProfile.Item.BODY);
remoteFolder.fetch(new Message[] { remoteMessage }, fp, null); remoteFolder.fetch(Collections.singletonList(remoteMessage), fp, null);
// Store the message locally and load the stored message into memory // Store the message locally and load the stored message into memory
localFolder.appendMessages(new Message[] { remoteMessage }); localFolder.appendMessages(Collections.singletonList(remoteMessage));
if (loadPartialFromSearch) { if (loadPartialFromSearch) {
fp.add(FetchProfile.Item.BODY); fp.add(FetchProfile.Item.BODY);
} }
fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.ENVELOPE);
message = localFolder.getMessage(uid); message = localFolder.getMessage(uid);
localFolder.fetch(new Message[] { message }, fp, null); localFolder.fetch(Collections.singletonList(message), fp, null);
// Mark that this message is now fully synched // Mark that this message is now fully synched
if (account.isMarkMessageAsReadOnView()) { if (account.isMarkMessageAsReadOnView()) {
@ -3124,9 +3104,7 @@ public class MessagingController implements Runnable {
FetchProfile fp = new FetchProfile(); FetchProfile fp = new FetchProfile();
fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.ENVELOPE);
fp.add(FetchProfile.Item.BODY); fp.add(FetchProfile.Item.BODY);
localFolder.fetch(new Message[] { localFolder.fetch(Collections.singletonList(message), fp, null);
message
}, fp, null);
localFolder.close(); localFolder.close();
for (MessagingListener l : getListeners(listener)) { for (MessagingListener l : getListeners(listener)) {
@ -3266,7 +3244,7 @@ public class MessagingController implements Runnable {
LocalStore localStore = account.getLocalStore(); LocalStore localStore = account.getLocalStore();
LocalFolder localFolder = localStore.getFolder(account.getOutboxFolderName()); LocalFolder localFolder = localStore.getFolder(account.getOutboxFolderName());
localFolder.open(Folder.OPEN_MODE_RW); localFolder.open(Folder.OPEN_MODE_RW);
localFolder.appendMessages(new Message[] { message }); localFolder.appendMessages(Collections.singletonList(message));
Message localMessage = localFolder.getMessage(message.getUid()); Message localMessage = localFolder.getMessage(message.getUid());
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true);
localFolder.close(); localFolder.close();
@ -3504,9 +3482,9 @@ public class MessagingController implements Runnable {
} }
localFolder.open(Folder.OPEN_MODE_RW); localFolder.open(Folder.OPEN_MODE_RW);
Message[] localMessages = localFolder.getMessages(null); List<? extends Message> localMessages = localFolder.getMessages(null);
int progress = 0; int progress = 0;
int todo = localMessages.length; int todo = localMessages.size();
for (MessagingListener l : getListeners()) { for (MessagingListener l : getListeners()) {
l.synchronizeMailboxProgress(account, account.getSentFolderName(), progress, todo); l.synchronizeMailboxProgress(account, account.getSentFolderName(), progress, todo);
} }
@ -3545,7 +3523,7 @@ public class MessagingController implements Runnable {
localFolder.fetch(new Message[] { message }, fp, null); localFolder.fetch(Collections.singletonList(message), fp, null);
try { try {
@ -3576,7 +3554,7 @@ public class MessagingController implements Runnable {
if (K9.DEBUG) if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Moving sent message to folder '" + account.getSentFolderName() + "' (" + localSentFolder.getId() + ") "); Log.i(K9.LOG_TAG, "Moving sent message to folder '" + account.getSentFolderName() + "' (" + localSentFolder.getId() + ") ");
localFolder.moveMessages(new Message[] { message }, localSentFolder); localFolder.moveMessages(Collections.singletonList(message), localSentFolder);
if (K9.DEBUG) if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Moved sent message to folder '" + account.getSentFolderName() + "' (" + localSentFolder.getId() + ") "); Log.i(K9.LOG_TAG, "Moved sent message to folder '" + account.getSentFolderName() + "' (" + localSentFolder.getId() + ") ");
@ -3594,7 +3572,7 @@ public class MessagingController implements Runnable {
// This is a complete hack, but is worlds better than the previous // This is a complete hack, but is worlds better than the previous
// "don't even bother" functionality // "don't even bother" functionality
if (getRootCauseMessage(e).startsWith("5")) { if (getRootCauseMessage(e).startsWith("5")) {
localFolder.moveMessages(new Message[] { message }, (LocalFolder) localStore.getFolder(account.getDraftsFolderName())); localFolder.moveMessages(Collections.singletonList(message), (LocalFolder) localStore.getFolder(account.getDraftsFolderName()));
} }
notifyUserIfCertificateProblem(mApplication, e, account, false); notifyUserIfCertificateProblem(mApplication, e, account, false);
@ -3679,14 +3657,14 @@ public class MessagingController implements Runnable {
// Collect accounts that belong to the search // Collect accounts that belong to the search
String[] accountUuids = search.getAccountUuids(); String[] accountUuids = search.getAccountUuids();
Account[] accounts; List<Account> accounts;
if (search.searchAllAccounts()) { if (search.searchAllAccounts()) {
accounts = preferences.getAccounts(); accounts = preferences.getAccounts();
} else { } else {
accounts = new Account[accountUuids.length]; accounts = new ArrayList<Account>(accountUuids.length);
for (int i = 0, len = accountUuids.length; i < len; i++) { for (int i = 0, len = accountUuids.length; i < len; i++) {
String accountUuid = accountUuids[i]; String accountUuid = accountUuids[i];
accounts[i] = preferences.getAccount(accountUuid); accounts.set(i, preferences.getAccount(accountUuid));
} }
} }
@ -3895,8 +3873,8 @@ public class MessagingController implements Runnable {
} }
} }
Message[] messages = localSrcFolder.getMessages(uids.toArray(EMPTY_STRING_ARRAY), null); List<? extends Message> messages = localSrcFolder.getMessages(uids.toArray(EMPTY_STRING_ARRAY), null);
if (messages.length > 0) { if (messages.size() > 0) {
Map<String, Message> origUidMap = new HashMap<String, Message>(); Map<String, Message> origUidMap = new HashMap<String, Message>();
for (Message message : messages) { for (Message message : messages) {
@ -3905,7 +3883,7 @@ public class MessagingController implements Runnable {
if (K9.DEBUG) if (K9.DEBUG)
Log.i(K9.LOG_TAG, "moveOrCopyMessageSynchronous: source folder = " + srcFolder Log.i(K9.LOG_TAG, "moveOrCopyMessageSynchronous: source folder = " + srcFolder
+ ", " + messages.length + " messages, " + ", destination folder = " + destFolder + ", isCopy = " + isCopy); + ", " + messages.size() + " messages, " + ", destination folder = " + destFolder + ", isCopy = " + isCopy);
if (isCopy) { if (isCopy) {
FetchProfile fp = new FetchProfile(); FetchProfile fp = new FetchProfile();
@ -4014,7 +3992,7 @@ public class MessagingController implements Runnable {
List<Message> messagesToDelete = collectMessagesInThreads(account, messages); List<Message> messagesToDelete = collectMessagesInThreads(account, messages);
deleteMessagesSynchronous(account, folderName, deleteMessagesSynchronous(account, folderName,
messagesToDelete.toArray(EMPTY_MESSAGE_ARRAY), null); messagesToDelete, null);
} catch (MessagingException e) { } catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Something went wrong while deleting threads", e); Log.e(K9.LOG_TAG, "Something went wrong while deleting threads", e);
} }
@ -4031,8 +4009,9 @@ public class MessagingController implements Runnable {
long rootId = localMessage.getRootId(); long rootId = localMessage.getRootId();
long threadId = (rootId == -1) ? localMessage.getThreadId() : rootId; long threadId = (rootId == -1) ? localMessage.getThreadId() : rootId;
Message[] messagesInThread = localStore.getMessagesInThread(threadId); List<? extends Message> messagesInThread = localStore.getMessagesInThread(threadId);
Collections.addAll(messagesInThreads, messagesInThread);
messagesInThreads.addAll(messagesInThread);
} }
return messagesInThreads; return messagesInThreads;
@ -4050,7 +4029,7 @@ public class MessagingController implements Runnable {
@Override @Override
public void run() { public void run() {
deleteMessagesSynchronous(account, folder.getName(), deleteMessagesSynchronous(account, folder.getName(),
accountMessages.toArray(EMPTY_MESSAGE_ARRAY), listener); accountMessages, listener);
} }
}); });
} }
@ -4059,7 +4038,7 @@ public class MessagingController implements Runnable {
} }
private void deleteMessagesSynchronous(final Account account, final String folder, final Message[] messages, private void deleteMessagesSynchronous(final Account account, final String folder, final List<? extends Message> messages,
MessagingListener listener) { MessagingListener listener) {
Folder localFolder = null; Folder localFolder = null;
Folder localTrashFolder = null; Folder localTrashFolder = null;
@ -4079,7 +4058,7 @@ public class MessagingController implements Runnable {
if (K9.DEBUG) if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Deleting messages in trash folder or trash set to -None-, not copying"); Log.d(K9.LOG_TAG, "Deleting messages in trash folder or trash set to -None-, not copying");
localFolder.setFlags(messages, new Flag[] { Flag.DELETED }, true); localFolder.setFlags(messages, Collections.singleton(Flag.DELETED), true);
} else { } else {
localTrashFolder = localStore.getFolder(account.getTrashFolderName()); localTrashFolder = localStore.getFolder(account.getTrashFolderName());
if (!localTrashFolder.exists()) { if (!localTrashFolder.exists()) {
@ -4147,10 +4126,10 @@ public class MessagingController implements Runnable {
} }
} }
private String[] getUidsFromMessages(Message[] messages) { private String[] getUidsFromMessages(List <? extends Message> messages) {
String[] uids = new String[messages.length]; String[] uids = new String[messages.size()];
for (int i = 0; i < messages.length; i++) { for (int i = 0; i < messages.size(); i++) {
uids[i] = messages[i].getUid(); uids[i] = messages.get(i).getUid();
} }
return uids; return uids;
} }
@ -4162,7 +4141,7 @@ public class MessagingController implements Runnable {
try { try {
if (remoteFolder.exists()) { if (remoteFolder.exists()) {
remoteFolder.open(Folder.OPEN_MODE_RW); remoteFolder.open(Folder.OPEN_MODE_RW);
remoteFolder.setFlags(new Flag [] { Flag.DELETED }, true); remoteFolder.setFlags(Collections.singleton(Flag.DELETED), true);
if (Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) { if (Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) {
remoteFolder.expunge(); remoteFolder.expunge();
} }
@ -4193,7 +4172,7 @@ public class MessagingController implements Runnable {
if (isTrashLocalOnly) { if (isTrashLocalOnly) {
localFolder.clearAllMessages(); localFolder.clearAllMessages();
} else { } else {
localFolder.setFlags(new Flag[] { Flag.DELETED }, true); localFolder.setFlags(Collections.singleton(Flag.DELETED), true);
} }
for (MessagingListener l : getListeners()) { for (MessagingListener l : getListeners()) {
@ -4863,7 +4842,8 @@ public class MessagingController implements Runnable {
String accountDescr = (account.getDescription() != null) ? String accountDescr = (account.getDescription() != null) ?
account.getDescription() : account.getEmail(); account.getDescription() : account.getEmail();
final ArrayList<MessageReference> allRefs = data.getAllMessageRefs(); final ArrayList<MessageReference> allRefs = new ArrayList<MessageReference>();
data.supplyAllMessageRefs(allRefs);
if (platformSupportsExtendedNotifications() && !privacyModeEnabled) { if (platformSupportsExtendedNotifications() && !privacyModeEnabled) {
if (newMessages > 1) { if (newMessages > 1) {
@ -5030,7 +5010,7 @@ public class MessagingController implements Runnable {
} }
private boolean skipAccountsInBackStack(Context context) { private boolean skipAccountsInBackStack(Context context) {
return Preferences.getPreferences(context).getAccounts().length == 1; return Preferences.getPreferences(context).getAccounts().size() == 1;
} }
/** /**
@ -5112,9 +5092,7 @@ public class MessagingController implements Runnable {
} }
// Save the message to the store. // Save the message to the store.
localFolder.appendMessages(new Message[] { localFolder.appendMessages(Collections.singletonList(message));
message
});
// Fetch the message back from the store. This is the Message that's returned to the caller. // Fetch the message back from the store. This is the Message that's returned to the caller.
localMessage = localFolder.getMessage(message.getUid()); localMessage = localFolder.getMessage(message.getUid());
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true);
@ -5424,7 +5402,7 @@ public class MessagingController implements Runnable {
return taccount.getDescription() + ":" + tfolderName; return taccount.getDescription() + ":" + tfolderName;
} }
static class MemorizingListener extends MessagingListener { static class MemorizingListener extends MessagingListener {
HashMap<String, Memory> memories = new HashMap<String, Memory>(31); Map<String, Memory> memories = new HashMap<String, Memory>(31);
Memory getMemory(Account account, String folderName) { Memory getMemory(Account account, String folderName) {
Memory memory = memories.get(getMemoryKey(account, folderName)); Memory memory = memories.get(getMemoryKey(account, folderName));

View File

@ -11,8 +11,8 @@ import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.PushReceiver; import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalFolder; import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.service.SleepService; import com.fsck.k9.service.SleepService;
import java.util.List; import java.util.List;

View File

@ -33,7 +33,7 @@ public class MessagingListener {
public void listFoldersStarted(Account account) {} public void listFoldersStarted(Account account) {}
public void listFolders(Account account, Folder[] folders) {} public void listFolders(Account account, List<? extends Folder> folders) {}
public void listFoldersFinished(Account account) {} public void listFoldersFinished(Account account) {}

View File

@ -88,8 +88,8 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalFolder; import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.provider.EmailProvider; import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.MessageColumns; import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.provider.EmailProvider.SpecialColumns; import com.fsck.k9.provider.EmailProvider.SpecialColumns;
@ -960,16 +960,16 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
accountUuids[0].equals(SearchSpecification.ALL_ACCOUNTS)) { accountUuids[0].equals(SearchSpecification.ALL_ACCOUNTS)) {
mAllAccounts = true; mAllAccounts = true;
Account[] accounts = mPreferences.getAccounts(); List<Account> accounts = mPreferences.getAccounts();
mAccountUuids = new String[accounts.length]; mAccountUuids = new String[accounts.size()];
for (int i = 0, len = accounts.length; i < len; i++) { for (int i = 0, len = accounts.size(); i < len; i++) {
mAccountUuids[i] = accounts[i].getUuid(); mAccountUuids[i] = accounts.get(i).getUuid();
} }
if (mAccountUuids.length == 1) { if (mAccountUuids.length == 1) {
mSingleAccountMode = true; mSingleAccountMode = true;
mAccount = accounts[0]; mAccount = accounts.get(0);
} }
} else { } else {
mAccountUuids = accountUuids; mAccountUuids = accountUuids;
@ -1084,11 +1084,11 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
mController.addListener(mListener); mController.addListener(mListener);
//Cancel pending new mail notifications when we open an account //Cancel pending new mail notifications when we open an account
Account[] accountsWithNotification; List<Account> accountsWithNotification;
Account account = mAccount; Account account = mAccount;
if (account != null) { if (account != null) {
accountsWithNotification = new Account[] { account }; accountsWithNotification = Collections.singletonList(account);
} else { } else {
accountsWithNotification = mPreferences.getAccounts(); accountsWithNotification = mPreferences.getAccounts();
} }
@ -1814,7 +1814,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
} }
List<String> folderNames = mSearch.getFolderNames(); List<String> folderNames = mSearch.getFolderNames();
return (folderNames.size() == 0 || folderNames.contains(folder)); return (folderNames.isEmpty() || folderNames.contains(folder));
} }
} }
@ -2362,7 +2362,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
} }
private void setFlagForSelected(final Flag flag, final boolean newState) { private void setFlagForSelected(final Flag flag, final boolean newState) {
if (mSelected.size() == 0) { if (mSelected.isEmpty()) {
return; return;
} }
@ -2586,7 +2586,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
private boolean checkCopyOrMovePossible(final List<Message> messages, private boolean checkCopyOrMovePossible(final List<Message> messages,
final FolderOperation operation) { final FolderOperation operation) {
if (messages.size() == 0) { if (messages.isEmpty()) {
return false; return false;
} }
@ -2995,8 +2995,8 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
super.onStop(); super.onStop();
} }
public ArrayList<MessageReference> getMessageReferences() { public List<MessageReference> getMessageReferences() {
ArrayList<MessageReference> messageRefs = new ArrayList<MessageReference>(); List<MessageReference> messageRefs = new ArrayList<MessageReference>();
for (int i = 0, len = mAdapter.getCount(); i < len; i++) { for (int i = 0, len = mAdapter.getCount(); i < len; i++) {
Cursor cursor = (Cursor) mAdapter.getItem(i); Cursor cursor = (Cursor) mAdapter.getItem(i);
@ -3515,7 +3515,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
} }
private void cleanupSelected(Cursor cursor) { private void cleanupSelected(Cursor cursor) {
if (mSelected.size() == 0) { if (mSelected.isEmpty()) {
return; return;
} }
@ -3534,7 +3534,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* Starts or finishes the action mode when necessary. * Starts or finishes the action mode when necessary.
*/ */
private void resetActionMode() { private void resetActionMode() {
if (mSelected.size() == 0) { if (mSelected.isEmpty()) {
if (mActionMode != null) { if (mActionMode != null) {
mActionMode.finish(); mActionMode.finish();
} }

View File

@ -39,7 +39,7 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part; import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.store.LocalStore.LocalMessage; import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.view.AttachmentView; import com.fsck.k9.view.AttachmentView;
import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback; import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback;
import com.fsck.k9.view.MessageHeader; import com.fsck.k9.view.MessageHeader;
@ -369,7 +369,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
if (mMessage != null) { if (mMessage != null) {
boolean newState = !mMessage.isSet(Flag.FLAGGED); boolean newState = !mMessage.isSet(Flag.FLAGGED);
mController.setFlag(mAccount, mMessage.getFolder().getName(), mController.setFlag(mAccount, mMessage.getFolder().getName(),
new Message[] { mMessage }, Flag.FLAGGED, newState); Collections.singletonList(mMessage), Flag.FLAGGED, newState);
mMessageView.setHeaders(mMessage, mAccount); mMessageView.setHeaders(mMessage, mAccount);
} }
} }
@ -485,7 +485,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
public void onToggleRead() { public void onToggleRead() {
if (mMessage != null) { if (mMessage != null) {
mController.setFlag(mAccount, mMessage.getFolder().getName(), mController.setFlag(mAccount, mMessage.getFolder().getName(),
new Message[] { mMessage }, Flag.SEEN, !mMessage.isSet(Flag.SEEN)); Collections.singletonList(mMessage), Flag.SEEN, !mMessage.isSet(Flag.SEEN));
mMessageView.setHeaders(mMessage, mAccount); mMessageView.setHeaders(mMessage, mAccount);
String subject = mMessage.getSubject(); String subject = mMessage.getSubject();
displayMessageSubject(subject); displayMessageSubject(subject);

View File

@ -13,6 +13,7 @@ import com.fsck.k9.K9;
import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Address;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
/** /**
* Helper class to access the contacts stored on the device. * Helper class to access the contacts stored on the device.
@ -276,7 +277,7 @@ public class Contacts {
*/ */
public ContactItem extractInfoFromContactPickerIntent(final Intent intent) { public ContactItem extractInfoFromContactPickerIntent(final Intent intent) {
Cursor cursor = null; Cursor cursor = null;
ArrayList<String> email = new ArrayList<String>(); List<String> email = new ArrayList<String>();
try { try {
Uri result = intent.getData(); Uri result = intent.getData();
@ -301,7 +302,7 @@ public class Contacts {
} }
// Return 'null' if no email addresses have been found // Return 'null' if no email addresses have been found
if (email.size() == 0) { if (email.isEmpty()) {
return null; return null;
} }

View File

@ -0,0 +1,33 @@
package com.fsck.k9.helper;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
/**
* Wraps the java.net.URLDecoder to avoid unhelpful checked exceptions.
*/
public class UrlEncodingHelper {
public static String decodeUtf8(String s) {
try {
return URLDecoder.decode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
/*
* This is impossible, UTF-8 is always supported
*/
throw new RuntimeException("UTF-8 not found");
}
}
public static String encodeUtf8(String s) {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
/*
* This is impossible, UTF-8 is always supported
*/
throw new RuntimeException("UTF-8 not found");
}
}
}

View File

@ -20,10 +20,13 @@ import com.fsck.k9.mail.filter.Base64;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.UnsupportedEncodingException; import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -102,6 +105,22 @@ public class Utility {
return TextUtils.join(String.valueOf(separator), parts); return TextUtils.join(String.valueOf(separator), parts);
} }
/**
* Combines the given Objects into a single String using
* each Object's toString() method and the separator character
* between each part.
*
* @param parts
* @param separator
* @return new String
*/
public static String combine(Iterable<?> parts, char separator) {
if (parts == null) {
return null;
}
return TextUtils.join(String.valueOf(separator), parts);
}
public static String base64Decode(String encoded) { public static String base64Decode(String encoded) {
if (encoded == null) { if (encoded == null) {
return null; return null;
@ -189,8 +208,8 @@ public class Utility {
* hundreds of times in places that slow down the UI, so it helps. * hundreds of times in places that slow down the UI, so it helps.
*/ */
public static String fastUrlDecode(String s) { public static String fastUrlDecode(String s) {
try {
byte[] bytes = s.getBytes("UTF-8"); byte[] bytes = s.getBytes(Charset.forName("UTF-8"));
byte ch; byte ch;
int length = 0; int length = 0;
for (int i = 0, count = bytes.length; i < count; i++) { for (int i = 0, count = bytes.length; i < count; i++) {
@ -213,10 +232,8 @@ public class Utility {
} }
length++; length++;
} }
return new String(bytes, 0, length, "UTF-8"); return new String(bytes, 0, length, Charset.forName("UTF-8"));
} catch (UnsupportedEncodingException uee) {
return null;
}
} }
/* /*
@ -706,4 +723,5 @@ public class Utility {
} }
return sMainThreadHandler; return sMainThreadHandler;
} }
} }

View File

@ -44,9 +44,9 @@ public class Address {
*/ */
private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0]; private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
String mAddress; private String mAddress;
String mPersonal; private String mPersonal;
public Address(Address address) { public Address(Address address) {
@ -315,7 +315,7 @@ public class Address {
if (addressList == null) { if (addressList == null) {
return new Address[] { }; return new Address[] { };
} }
ArrayList<Address> addresses = new ArrayList<Address>(); List<Address> addresses = new ArrayList<Address>();
int length = addressList.length(); int length = addressList.length();
int pairStartIndex = 0; int pairStartIndex = 0;
int pairEndIndex = 0; int pairEndIndex = 0;

View File

@ -8,7 +8,21 @@ import java.io.OutputStream;
import com.fsck.k9.mail.store.UnavailableStorageException; import com.fsck.k9.mail.store.UnavailableStorageException;
public interface Body { public interface Body {
/**
* Returns the raw data of the body, without transfer encoding etc applied.
* TODO perhaps it would be better to have an intermediate "simple part" class where this method could reside
* because it makes no sense for multiparts
*/
public InputStream getInputStream() throws MessagingException; public InputStream getInputStream() throws MessagingException;
/**
* Sets the content transfer encoding (7bit, 8bit, quoted-printable or base64).
*/
public void setEncoding(String encoding) throws UnavailableStorageException, MessagingException; public void setEncoding(String encoding) throws UnavailableStorageException, MessagingException;
/**
* Writes the body's data to the given {@link OutputStream}.
* The written data is transfer encoded (e.g. transformed to Base64 when needed).
*/
public void writeTo(OutputStream out) throws IOException, MessagingException; public void writeTo(OutputStream out) throws IOException, MessagingException;
} }

View File

@ -14,5 +14,6 @@ public abstract class BodyPart implements Part {
public abstract void setEncoding(String encoding) throws MessagingException; public abstract void setEncoding(String encoding) throws MessagingException;
@Override
public abstract void setUsing7bitTransport() throws MessagingException; public abstract void setUsing7bitTransport() throws MessagingException;
} }

View File

@ -13,14 +13,8 @@ public class CertificateChainException extends CertificateException {
private static final long serialVersionUID = 1103894512106650107L; private static final long serialVersionUID = 1103894512106650107L;
private X509Certificate[] mCertChain; private X509Certificate[] mCertChain;
public CertificateChainException(String msg, X509Certificate[] chain) { public CertificateChainException(String msg, X509Certificate[] chain, Throwable cause) {
super(msg); super(msg, cause);
setCertChain(chain);
}
public CertificateChainException(CertificateException ce,
X509Certificate[] chain) {
super.initCause(ce);
setCertChain(chain); setCertChain(chain);
} }

View File

@ -23,7 +23,6 @@ public interface CompositeBody extends Body {
* @throws MessagingException * @throws MessagingException
* *
*/ */
public abstract void setUsing7bitTransport() throws MessagingException; public abstract void setUsing7bitTransport() throws MessagingException;
} }

View File

@ -1,8 +1,10 @@
package com.fsck.k9.mail; package com.fsck.k9.mail;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import android.util.Log; import android.util.Log;
import com.fsck.k9.Account; import com.fsck.k9.Account;
@ -92,7 +94,7 @@ public abstract class Folder {
* @return List of messages * @return List of messages
* @throws MessagingException * @throws MessagingException
*/ */
public abstract Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) throws MessagingException; public abstract List<? extends Message> getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) throws MessagingException;
/** /**
* Fetches the given list of messages. The specified listener is notified as * Fetches the given list of messages. The specified listener is notified as
@ -102,36 +104,36 @@ public abstract class Folder {
* @param listener Listener to notify as we download messages. * @param listener Listener to notify as we download messages.
* @return List of messages * @return List of messages
*/ */
public abstract Message[] getMessages(MessageRetrievalListener listener) throws MessagingException; public abstract List<? extends Message> getMessages(MessageRetrievalListener listener) throws MessagingException;
public Message[] getMessages(MessageRetrievalListener listener, boolean includeDeleted) throws MessagingException { public List<? extends Message> getMessages(MessageRetrievalListener listener, boolean includeDeleted) throws MessagingException {
return getMessages(listener); return getMessages(listener);
} }
public abstract Message[] getMessages(String[] uids, MessageRetrievalListener listener) public abstract List<? extends Message> getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException; throws MessagingException;
public abstract Map<String, String> appendMessages(Message[] messages) throws MessagingException; public abstract Map<String, String> appendMessages(List<? extends Message> messages) throws MessagingException;
public Map<String, String> copyMessages(Message[] msgs, Folder folder) throws MessagingException { public Map<String, String> copyMessages(List<? extends Message> msgs, Folder folder) throws MessagingException {
return null; return null;
} }
public Map<String, String> moveMessages(Message[] msgs, Folder folder) throws MessagingException { public Map<String, String> moveMessages(List<? extends Message> msgs, Folder folder) throws MessagingException {
return null; return null;
} }
public void delete(Message[] msgs, String trashFolderName) throws MessagingException { public void delete(List<? extends Message> msgs, String trashFolderName) throws MessagingException {
for (Message message : msgs) { for (Message message : msgs) {
Message myMessage = getMessage(message.getUid()); Message myMessage = getMessage(message.getUid());
myMessage.delete(trashFolderName); myMessage.delete(trashFolderName);
} }
} }
public abstract void setFlags(Message[] messages, Flag[] flags, boolean value) public abstract void setFlags(List<? extends Message> messages, Set<Flag> flags, boolean value)
throws MessagingException; throws MessagingException;
public abstract void setFlags(Flag[] flags, boolean value) throws MessagingException; public abstract void setFlags(Set<Flag> flags, boolean value) throws MessagingException;
public abstract String getUidFromMessageId(Message message) throws MessagingException; public abstract String getUidFromMessageId(Message message) throws MessagingException;
@ -146,7 +148,7 @@ public abstract class Folder {
* @param listener Listener to notify as we fetch messages. * @param listener Listener to notify as we fetch messages.
* @throws MessagingException * @throws MessagingException
*/ */
public abstract void fetch(Message[] messages, FetchProfile fp, public abstract void fetch(List<? extends Message> messages, FetchProfile fp,
MessageRetrievalListener listener) throws MessagingException; MessageRetrievalListener listener) throws MessagingException;
public void fetchPart(Message message, Part part, public void fetchPart(Message message, Part part,
@ -243,7 +245,7 @@ public abstract class Folder {
return mAccount; return mAccount;
} }
public List<Message> search(String queryString, final Flag[] requiredFlags, final Flag[] forbiddenFlags) public List<Message> search(String queryString, final Set<Flag> requiredFlags, final Set<Flag> forbiddenFlags)
throws MessagingException { throws MessagingException {
throw new MessagingException("K-9 does not support searches on this folder type"); throw new MessagingException("K-9 does not support searches on this folder type");
} }

View File

@ -2,7 +2,10 @@
package com.fsck.k9.mail; package com.fsck.k9.mail;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.EnumSet;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -16,7 +19,6 @@ import com.fsck.k9.mail.store.UnavailableStorageException;
public abstract class Message implements Part, CompositeBody { public abstract class Message implements Part, CompositeBody {
private static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
private MessageReference mReference = null; private MessageReference mReference = null;
@ -26,9 +28,9 @@ public abstract class Message implements Part, CompositeBody {
protected String mUid; protected String mUid;
protected Set<Flag> mFlags = new HashSet<Flag>(); private Set<Flag> mFlags = EnumSet.noneOf(Flag.class);
protected Date mInternalDate; private Date mInternalDate;
protected Folder mFolder; protected Folder mFolder;
@ -45,6 +47,7 @@ public abstract class Message implements Part, CompositeBody {
} }
return false; return false;
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (o == null || !(o instanceof Message)) { if (o == null || !(o instanceof Message)) {
@ -123,20 +126,27 @@ public abstract class Message implements Part, CompositeBody {
public abstract void setReferences(String references) throws MessagingException; public abstract void setReferences(String references) throws MessagingException;
@Override
public abstract Body getBody(); public abstract Body getBody();
@Override
public abstract String getContentType() throws MessagingException; public abstract String getContentType() throws MessagingException;
@Override
public abstract void addHeader(String name, String value) throws MessagingException; public abstract void addHeader(String name, String value) throws MessagingException;
@Override
public abstract void setHeader(String name, String value) throws MessagingException; public abstract void setHeader(String name, String value) throws MessagingException;
@Override
public abstract String[] getHeader(String name) throws MessagingException; public abstract String[] getHeader(String name) throws MessagingException;
public abstract Set<String> getHeaderNames() throws UnavailableStorageException; public abstract Set<String> getHeaderNames() throws UnavailableStorageException;
@Override
public abstract void removeHeader(String name) throws MessagingException; public abstract void removeHeader(String name) throws MessagingException;
@Override
public abstract void setBody(Body body) throws MessagingException; public abstract void setBody(Body body) throws MessagingException;
public abstract long getId(); public abstract long getId();
@ -144,6 +154,8 @@ public abstract class Message implements Part, CompositeBody {
public abstract String getPreview(); public abstract String getPreview();
public abstract boolean hasAttachments(); public abstract boolean hasAttachments();
public abstract int getSize();
/* /*
* calculateContentPreview * calculateContentPreview
* Takes a plain text message body as a string. * Takes a plain text message body as a string.
@ -198,8 +210,8 @@ public abstract class Message implements Part, CompositeBody {
/* /*
* TODO Refactor Flags at some point to be able to store user defined flags. * TODO Refactor Flags at some point to be able to store user defined flags.
*/ */
public Flag[] getFlags() { public Set<Flag> getFlags() {
return mFlags.toArray(EMPTY_FLAG_ARRAY); return Collections.unmodifiableSet(mFlags);
} }
/** /**
@ -223,7 +235,7 @@ public abstract class Message implements Part, CompositeBody {
* @param flags * @param flags
* @param set * @param set
*/ */
public void setFlags(Flag[] flags, boolean set) throws MessagingException { public void setFlags(final Set<Flag> flags, boolean set) throws MessagingException {
for (Flag flag : flags) { for (Flag flag : flags) {
setFlag(flag, set); setFlag(flag, set);
} }
@ -236,6 +248,7 @@ public abstract class Message implements Part, CompositeBody {
public void destroy() throws MessagingException {} public void destroy() throws MessagingException {}
@Override
public abstract void setEncoding(String encoding) throws UnavailableStorageException, MessagingException; public abstract void setEncoding(String encoding) throws UnavailableStorageException, MessagingException;
public abstract void setCharset(String charset) throws MessagingException; public abstract void setCharset(String charset) throws MessagingException;
@ -279,7 +292,7 @@ public abstract class Message implements Part, CompositeBody {
destination.mReference = mReference; destination.mReference = mReference;
// mFlags contents can change during the object lifetime, so copy the Set // mFlags contents can change during the object lifetime, so copy the Set
destination.mFlags = new HashSet<Flag>(mFlags); destination.mFlags = EnumSet.copyOf(mFlags);
} }
/** /**
@ -293,6 +306,8 @@ public abstract class Message implements Part, CompositeBody {
* for more information. * for more information.
* </p> * </p>
*/ */
@Override
public abstract Message clone(); public abstract Message clone();
@Override
public abstract void setUsing7bitTransport() throws MessagingException; public abstract void setUsing7bitTransport() throws MessagingException;
} }

View File

@ -4,7 +4,7 @@ package com.fsck.k9.mail;
public class MessagingException extends Exception { public class MessagingException extends Exception {
public static final long serialVersionUID = -1; public static final long serialVersionUID = -1;
boolean permanentFailure = false; private boolean permanentFailure = false;
public MessagingException(String message) { public MessagingException(String message) {
super(message); super(message);
@ -28,9 +28,9 @@ public class MessagingException extends Exception {
return permanentFailure; return permanentFailure;
} }
//TODO setters in Exception are bad style, remove (it's nearly unused anyway)
public void setPermanentFailure(boolean permanentFailure) { public void setPermanentFailure(boolean permanentFailure) {
this.permanentFailure = permanentFailure; this.permanentFailure = permanentFailure;
} }
} }

View File

@ -2,6 +2,8 @@
package com.fsck.k9.mail; package com.fsck.k9.mail;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.james.mime4j.util.MimeUtil; import org.apache.james.mime4j.util.MimeUtil;
@ -9,26 +11,25 @@ import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody; import com.fsck.k9.mail.internet.TextBody;
public abstract class Multipart implements CompositeBody { public abstract class Multipart implements CompositeBody {
protected Part mParent; private Part mParent;
protected ArrayList<BodyPart> mParts = new ArrayList<BodyPart>(); private final List<BodyPart> mParts = new ArrayList<BodyPart>();
protected String mContentType; private String mContentType;
public void addBodyPart(BodyPart part) { public void addBodyPart(BodyPart part) {
mParts.add(part); mParts.add(part);
part.setParent(this); part.setParent(this);
} }
public void addBodyPart(BodyPart part, int index) {
mParts.add(index, part);
part.setParent(this);
}
public BodyPart getBodyPart(int index) { public BodyPart getBodyPart(int index) {
return mParts.get(index); return mParts.get(index);
} }
public List<BodyPart> getBodyParts() {
return Collections.unmodifiableList(mParts);
}
public String getContentType() { public String getContentType() {
return mContentType; return mContentType;
} }
@ -37,16 +38,6 @@ public abstract class Multipart implements CompositeBody {
return mParts.size(); return mParts.size();
} }
public boolean removeBodyPart(BodyPart part) {
part.setParent(null);
return mParts.remove(part);
}
public void removeBodyPart(int index) {
mParts.get(index).setParent(null);
mParts.remove(index);
}
public Part getParent() { public Part getParent() {
return mParent; return mParent;
} }
@ -55,6 +46,7 @@ public abstract class Multipart implements CompositeBody {
this.mParent = parent; this.mParent = parent;
} }
@Override
public void setEncoding(String encoding) throws MessagingException { public void setEncoding(String encoding) throws MessagingException {
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding) if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) { && !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {

View File

@ -21,8 +21,6 @@ public interface Part {
public String[] getHeader(String name) throws MessagingException; public String[] getHeader(String name) throws MessagingException;
public int getSize();
public boolean isMimeType(String mimeType) throws MessagingException; public boolean isMimeType(String mimeType) throws MessagingException;
public String getMimeType() throws MessagingException; public String getMimeType() throws MessagingException;
@ -42,5 +40,6 @@ public interface Part {
* @throws MessagingException * @throws MessagingException
* *
*/ */
//TODO perhaps it would be clearer to use a flag "force7bit" in writeTo
public abstract void setUsing7bitTransport() throws MessagingException; public abstract void setUsing7bitTransport() throws MessagingException;
} }

View File

@ -3,7 +3,9 @@ package com.fsck.k9.mail;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
@ -12,9 +14,9 @@ import android.util.Log;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.mail.store.ImapStore; import com.fsck.k9.mail.store.ImapStore;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.Pop3Store; import com.fsck.k9.mail.store.Pop3Store;
import com.fsck.k9.mail.store.StorageManager.StorageProvider; import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.store.UnavailableStorageException; import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.WebDavStore; import com.fsck.k9.mail.store.WebDavStore;
@ -33,19 +35,19 @@ public abstract class Store {
/** /**
* Remote stores indexed by Uri. * Remote stores indexed by Uri.
*/ */
private static HashMap<String, Store> sStores = new HashMap<String, Store>(); private static Map<String, Store> sStores = new HashMap<String, Store>();
/** /**
* Local stores indexed by UUID because the Uri may change due to migration to/from SD-card. * Local stores indexed by UUID because the Uri may change due to migration to/from SD-card.
*/ */
private static ConcurrentHashMap<String, Store> sLocalStores = new ConcurrentHashMap<String, Store>(); private static ConcurrentMap<String, Store> sLocalStores = new ConcurrentHashMap<String, Store>();
/** /**
* Lock objects indexed by account UUID. * Lock objects indexed by account UUID.
* *
* @see #getLocalInstance(Account, Application) * @see #getLocalInstance(Account, Application)
*/ */
private static ConcurrentHashMap<String, Object> sAccountLocks = new ConcurrentHashMap<String, Object>(); private static ConcurrentMap<String, Object> sAccountLocks = new ConcurrentHashMap<String, Object>();
/** /**
* Get an instance of a remote mail store. * Get an instance of a remote mail store.
@ -242,7 +244,7 @@ public abstract class Store {
return true; return true;
} }
public void sendMessages(Message[] messages) throws MessagingException { public void sendMessages(List<? extends Message> messages) throws MessagingException {
} }
public Pusher getPusher(PushReceiver receiver) { public Pusher getPusher(PushReceiver receiver) {

View File

@ -17,8 +17,8 @@
package com.fsck.k9.mail.filter; package com.fsck.k9.mail.filter;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.charset.Charset;
/** /**
* Provides Base64 encoding and decoding as defined by RFC 2045. * Provides Base64 encoding and decoding as defined by RFC 2045.
@ -225,12 +225,8 @@ public class Base64 {
} }
this.decodeSize = encodeSize - 1; this.decodeSize = encodeSize - 1;
if (containsBase64Byte(lineSeparator)) { if (containsBase64Byte(lineSeparator)) {
String sep; String sep = new String(lineSeparator, Charset.forName("UTF-8"));
try {
sep = new String(lineSeparator, "UTF-8");
} catch (UnsupportedEncodingException uee) {
sep = new String(lineSeparator);
}
throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]"); throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]");
} }
} }

View File

@ -7,7 +7,8 @@ import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.nio.charset.Charset;
import org.apache.james.mime4j.codec.Base64InputStream; import org.apache.james.mime4j.codec.Base64InputStream;
import org.apache.james.mime4j.codec.QuotedPrintableInputStream; import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
import org.apache.james.mime4j.util.CharsetUtil; import org.apache.james.mime4j.util.CharsetUtil;
@ -30,12 +31,7 @@ public class DecoderUtil {
* @return the decoded string. * @return the decoded string.
*/ */
private static String decodeB(String encodedWord, String charset) { private static String decodeB(String encodedWord, String charset) {
byte[] bytes; byte[] bytes = encodedWord.getBytes(Charset.forName("US-ASCII"));
try {
bytes = encodedWord.getBytes("US-ASCII");
} catch (UnsupportedEncodingException e) {
return null;
}
Base64InputStream is = new Base64InputStream(new ByteArrayInputStream(bytes)); Base64InputStream is = new Base64InputStream(new ByteArrayInputStream(bytes));
try { try {
@ -68,12 +64,7 @@ public class DecoderUtil {
} }
} }
byte[] bytes; byte[] bytes = sb.toString().getBytes(Charset.forName("US-ASCII"));
try {
bytes = sb.toString().getBytes("US-ASCII");
} catch (UnsupportedEncodingException e) {
return null;
}
QuotedPrintableInputStream is = new QuotedPrintableInputStream(new ByteArrayInputStream(bytes)); QuotedPrintableInputStream is = new QuotedPrintableInputStream(new ByteArrayInputStream(bytes));
try { try {

View File

@ -20,9 +20,8 @@ import org.apache.james.mime4j.util.MimeUtil;
* Message. * Message.
*/ */
public class MimeBodyPart extends BodyPart { public class MimeBodyPart extends BodyPart {
protected MimeHeader mHeader = new MimeHeader(); private final MimeHeader mHeader = new MimeHeader();
protected Body mBody; private Body mBody;
protected int mSize;
public MimeBodyPart() throws MessagingException { public MimeBodyPart() throws MessagingException {
this(null); this(null);
@ -39,30 +38,36 @@ public class MimeBodyPart extends BodyPart {
setBody(body); setBody(body);
} }
protected String getFirstHeader(String name) { private String getFirstHeader(String name) {
return mHeader.getFirstHeader(name); return mHeader.getFirstHeader(name);
} }
@Override
public void addHeader(String name, String value) throws MessagingException { public void addHeader(String name, String value) throws MessagingException {
mHeader.addHeader(name, value); mHeader.addHeader(name, value);
} }
@Override
public void setHeader(String name, String value) { public void setHeader(String name, String value) {
mHeader.setHeader(name, value); mHeader.setHeader(name, value);
} }
@Override
public String[] getHeader(String name) throws MessagingException { public String[] getHeader(String name) throws MessagingException {
return mHeader.getHeader(name); return mHeader.getHeader(name);
} }
@Override
public void removeHeader(String name) throws MessagingException { public void removeHeader(String name) throws MessagingException {
mHeader.removeHeader(name); mHeader.removeHeader(name);
} }
@Override
public Body getBody() { public Body getBody() {
return mBody; return mBody;
} }
@Override
public void setBody(Body body) throws MessagingException { public void setBody(Body body) throws MessagingException {
this.mBody = body; this.mBody = body;
if (body instanceof Multipart) { if (body instanceof Multipart) {
@ -94,15 +99,18 @@ public class MimeBodyPart extends BodyPart {
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding); setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
} }
@Override
public String getContentType() throws MessagingException { public String getContentType() throws MessagingException {
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE); String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
return (contentType == null) ? "text/plain" : contentType; return (contentType == null) ? "text/plain" : contentType;
} }
@Override
public String getDisposition() throws MessagingException { public String getDisposition() throws MessagingException {
return getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION); return getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
} }
@Override
public String getContentId() throws MessagingException { public String getContentId() throws MessagingException {
String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID); String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID);
if (contentId == null) { if (contentId == null) {
@ -117,21 +125,20 @@ public class MimeBodyPart extends BodyPart {
contentId; contentId;
} }
@Override
public String getMimeType() throws MessagingException { public String getMimeType() throws MessagingException {
return MimeUtility.getHeaderParameter(getContentType(), null); return MimeUtility.getHeaderParameter(getContentType(), null);
} }
@Override
public boolean isMimeType(String mimeType) throws MessagingException { public boolean isMimeType(String mimeType) throws MessagingException {
return getMimeType().equalsIgnoreCase(mimeType); return getMimeType().equalsIgnoreCase(mimeType);
} }
public int getSize() {
return mSize;
}
/** /**
* Write the MimeMessage out in MIME format. * Write the MimeMessage out in MIME format.
*/ */
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException { public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
mHeader.writeTo(out); mHeader.writeTo(out);

View File

@ -36,7 +36,7 @@ public class MimeHeader {
HEADER_ANDROID_ATTACHMENT_STORE_DATA HEADER_ANDROID_ATTACHMENT_STORE_DATA
}; };
protected ArrayList<Field> mFields = new ArrayList<Field>(); private List<Field> mFields = new ArrayList<Field>();
private String mCharset = null; private String mCharset = null;
public void clear() { public void clear() {
@ -72,7 +72,7 @@ public class MimeHeader {
} }
public String[] getHeader(String name) { public String[] getHeader(String name) {
ArrayList<String> values = new ArrayList<String>(); List<String> values = new ArrayList<String>();
for (Field field : mFields) { for (Field field : mFields) {
if (field.name.equalsIgnoreCase(name)) { if (field.name.equalsIgnoreCase(name)) {
values.add(field.value); values.add(field.value);
@ -85,7 +85,7 @@ public class MimeHeader {
} }
public void removeHeader(String name) { public void removeHeader(String name) {
ArrayList<Field> removeFields = new ArrayList<Field>(); List<Field> removeFields = new ArrayList<Field>();
for (Field field : mFields) { for (Field field : mFields) {
if (field.name.equalsIgnoreCase(name)) { if (field.name.equalsIgnoreCase(name)) {
removeFields.add(field); removeFields.add(field);
@ -119,7 +119,7 @@ public class MimeHeader {
} }
// encode non printable characters except LF/CR/TAB codes. // encode non printable characters except LF/CR/TAB codes.
public boolean hasToBeEncoded(String text) { private boolean hasToBeEncoded(String text) {
for (int i = 0; i < text.length(); i++) { for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i); char c = text.charAt(i);
if ((c < 0x20 || 0x7e < c) && // non printable if ((c < 0x20 || 0x7e < c) && // non printable
@ -131,10 +131,10 @@ public class MimeHeader {
return false; return false;
} }
static class Field { private static class Field {
final String name; private final String name;
final String value; private final String value;
public Field(String name, String value) { public Field(String name, String value) {
this.name = name; this.name = name;
@ -153,6 +153,7 @@ public class MimeHeader {
mCharset = charset; mCharset = charset;
} }
@Override
public MimeHeader clone() { public MimeHeader clone() {
MimeHeader header = new MimeHeader(); MimeHeader header = new MimeHeader();
header.mCharset = mCharset; header.mCharset = mCharset;

View File

@ -41,7 +41,7 @@ import com.fsck.k9.K9;
* RFC 2045 style headers. * RFC 2045 style headers.
*/ */
public class MimeMessage extends Message { public class MimeMessage extends Message {
protected MimeHeader mHeader = new MimeHeader(); private MimeHeader mHeader = new MimeHeader();
protected Address[] mFrom; protected Address[] mFrom;
protected Address[] mTo; protected Address[] mTo;
protected Address[] mCc; protected Address[] mCc;
@ -49,33 +49,19 @@ public class MimeMessage extends Message {
protected Address[] mReplyTo; protected Address[] mReplyTo;
protected String mMessageId; protected String mMessageId;
protected String[] mReferences; private String[] mReferences;
protected String[] mInReplyTo; private String[] mInReplyTo;
protected Date mSentDate; private Date mSentDate;
protected SimpleDateFormat mDateFormat; private SimpleDateFormat mDateFormat;
protected Body mBody; private Body mBody;
protected int mSize; protected int mSize;
public MimeMessage() { public MimeMessage() {
} }
/**
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
* Nested messages will not be recursively parsed.
*
* @param in
* @throws IOException
* @throws MessagingException
*
* @see #MimeMessage(InputStream in, boolean recurse)
*/
public MimeMessage(InputStream in) throws IOException, MessagingException {
parse(in);
}
/** /**
* Parse the given InputStream using Apache Mime4J to build a MimeMessage. * Parse the given InputStream using Apache Mime4J to build a MimeMessage.
* *
@ -88,11 +74,15 @@ public class MimeMessage extends Message {
parse(in, recurse); parse(in, recurse);
} }
protected void parse(InputStream in) throws IOException, MessagingException { /**
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
* Does not recurse through nested bodyparts.
*/
public final void parse(InputStream in) throws IOException, MessagingException {
parse(in, false); parse(in, false);
} }
protected void parse(InputStream in, boolean recurse) throws IOException, MessagingException { private void parse(InputStream in, boolean recurse) throws IOException, MessagingException {
mHeader.clear(); mHeader.clear();
mFrom = null; mFrom = null;
mTo = null; mTo = null;
@ -121,8 +111,8 @@ public class MimeMessage extends Message {
try { try {
parser.parse(new EOLConvertingInputStream(in)); parser.parse(new EOLConvertingInputStream(in));
} catch (MimeException me) { } catch (MimeException me) {
//TODO wouldn't a MessagingException be better?
throw new Error(me); throw new Error(me);
} }
} }
@ -177,20 +167,25 @@ public class MimeMessage extends Message {
return (contentType == null) ? "text/plain" : contentType; return (contentType == null) ? "text/plain" : contentType;
} }
@Override
public String getDisposition() throws MessagingException { public String getDisposition() throws MessagingException {
return getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION); return getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
} }
@Override
public String getContentId() throws MessagingException { public String getContentId() throws MessagingException {
return null; return null;
} }
@Override
public String getMimeType() throws MessagingException { public String getMimeType() throws MessagingException {
return MimeUtility.getHeaderParameter(getContentType(), null); return MimeUtility.getHeaderParameter(getContentType(), null);
} }
@Override
public boolean isMimeType(String mimeType) throws MessagingException { public boolean isMimeType(String mimeType) throws MessagingException {
return getMimeType().equalsIgnoreCase(mimeType); return getMimeType().equalsIgnoreCase(mimeType);
} }
@Override
public int getSize() { public int getSize() {
return mSize; return mSize;
} }
@ -419,7 +414,7 @@ public class MimeMessage extends Message {
} }
} }
protected String getFirstHeader(String name) { private String getFirstHeader(String name) {
return mHeader.getFirstHeader(name); return mHeader.getFirstHeader(name);
} }
@ -448,6 +443,7 @@ public class MimeMessage extends Message {
return mHeader.getHeaderNames(); return mHeader.getHeaderNames();
} }
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException { public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
@ -459,6 +455,7 @@ public class MimeMessage extends Message {
} }
} }
@Override
public InputStream getInputStream() throws MessagingException { public InputStream getInputStream() throws MessagingException {
return null; return null;
} }
@ -482,7 +479,7 @@ public class MimeMessage extends Message {
} }
} }
class MimeMessageBuilder implements ContentHandler { private class MimeMessageBuilder implements ContentHandler {
private final LinkedList<Object> stack = new LinkedList<Object>(); private final LinkedList<Object> stack = new LinkedList<Object>();
public MimeMessageBuilder() { public MimeMessageBuilder() {
@ -495,6 +492,7 @@ public class MimeMessage extends Message {
} }
} }
@Override
public void startMessage() { public void startMessage() {
if (stack.isEmpty()) { if (stack.isEmpty()) {
stack.addFirst(MimeMessage.this); stack.addFirst(MimeMessage.this);
@ -510,22 +508,23 @@ public class MimeMessage extends Message {
} }
} }
@Override
public void endMessage() { public void endMessage() {
expect(MimeMessage.class); expect(MimeMessage.class);
stack.removeFirst(); stack.removeFirst();
} }
@Override
public void startHeader() { public void startHeader() {
expect(Part.class); expect(Part.class);
} }
@Override
public void endHeader() { public void endHeader() {
expect(Part.class); expect(Part.class);
} }
@Override
public void startMultipart(BodyDescriptor bd) { public void startMultipart(BodyDescriptor bd) {
expect(Part.class); expect(Part.class);
@ -539,6 +538,7 @@ public class MimeMessage extends Message {
} }
} }
@Override
public void body(BodyDescriptor bd, InputStream in) throws IOException { public void body(BodyDescriptor bd, InputStream in) throws IOException {
expect(Part.class); expect(Part.class);
try { try {
@ -550,10 +550,12 @@ public class MimeMessage extends Message {
} }
} }
@Override
public void endMultipart() { public void endMultipart() {
stack.removeFirst(); stack.removeFirst();
} }
@Override
public void startBodyPart() { public void startBodyPart() {
expect(MimeMultipart.class); expect(MimeMultipart.class);
@ -566,21 +568,13 @@ public class MimeMessage extends Message {
} }
} }
@Override
public void endBodyPart() { public void endBodyPart() {
expect(BodyPart.class); expect(BodyPart.class);
stack.removeFirst(); stack.removeFirst();
} }
public void epilogue(InputStream is) throws IOException { @Override
expect(MimeMultipart.class);
StringBuilder sb = new StringBuilder();
int b;
while ((b = is.read()) != -1) {
sb.append((char)b);
}
// ((Multipart) stack.peek()).setEpilogue(sb.toString());
}
public void preamble(InputStream is) throws IOException { public void preamble(InputStream is) throws IOException {
expect(MimeMultipart.class); expect(MimeMultipart.class);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@ -589,9 +583,13 @@ public class MimeMessage extends Message {
sb.append((char)b); sb.append((char)b);
} }
((MimeMultipart)stack.peek()).setPreamble(sb.toString()); ((MimeMultipart)stack.peek()).setPreamble(sb.toString());
} }
@Override
public void epilogue(InputStream is) throws IOException {
}
@Override
public void raw(InputStream is) throws IOException { public void raw(InputStream is) throws IOException {
throw new UnsupportedOperationException("Not supported"); throw new UnsupportedOperationException("Not supported");
} }
@ -641,14 +639,17 @@ public class MimeMessage extends Message {
return message; return message;
} }
@Override
public long getId() { public long getId() {
return Long.parseLong(mUid); //or maybe .mMessageId? return Long.parseLong(mUid); //or maybe .mMessageId?
} }
@Override
public String getPreview() { public String getPreview() {
return ""; return "";
} }
@Override
public boolean hasAttachments() { public boolean hasAttachments() {
return false; return false;
} }

View File

@ -10,13 +10,11 @@ import java.util.Locale;
import java.util.Random; import java.util.Random;
public class MimeMultipart extends Multipart { public class MimeMultipart extends Multipart {
protected String mPreamble; private String mPreamble;
protected String mContentType; private String mContentType;
protected String mBoundary; private final String mBoundary;
protected String mSubType;
public MimeMultipart() throws MessagingException { public MimeMultipart() throws MessagingException {
mBoundary = generateBoundary(); mBoundary = generateBoundary();
@ -26,7 +24,6 @@ public class MimeMultipart extends Multipart {
public MimeMultipart(String contentType) throws MessagingException { public MimeMultipart(String contentType) throws MessagingException {
this.mContentType = contentType; this.mContentType = contentType;
try { try {
mSubType = MimeUtility.getHeaderParameter(contentType, null).split("/")[1];
mBoundary = MimeUtility.getHeaderParameter(contentType, "boundary"); mBoundary = MimeUtility.getHeaderParameter(contentType, "boundary");
if (mBoundary == null) { if (mBoundary == null) {
throw new MessagingException("MultiPart does not contain boundary: " + contentType); throw new MessagingException("MultiPart does not contain boundary: " + contentType);
@ -62,10 +59,10 @@ public class MimeMultipart extends Multipart {
} }
public void setSubType(String subType) { public void setSubType(String subType) {
this.mSubType = subType;
mContentType = String.format("multipart/%s; boundary=\"%s\"", subType, mBoundary); mContentType = String.format("multipart/%s; boundary=\"%s\"", subType, mBoundary);
} }
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException { public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
@ -74,14 +71,12 @@ public class MimeMultipart extends Multipart {
writer.write("\r\n"); writer.write("\r\n");
} }
if (mParts.isEmpty()) { if (getBodyParts().isEmpty()) {
writer.write("--"); writer.write("--");
writer.write(mBoundary); writer.write(mBoundary);
writer.write("\r\n"); writer.write("\r\n");
} } else {
for (BodyPart bodyPart : getBodyParts()) {
for (int i = 0, count = mParts.size(); i < count; i++) {
BodyPart bodyPart = mParts.get(i);
writer.write("--"); writer.write("--");
writer.write(mBoundary); writer.write(mBoundary);
writer.write("\r\n"); writer.write("\r\n");
@ -89,6 +84,7 @@ public class MimeMultipart extends Multipart {
bodyPart.writeTo(out); bodyPart.writeTo(out);
writer.write("\r\n"); writer.write("\r\n");
} }
}
writer.write("--"); writer.write("--");
writer.write(mBoundary); writer.write(mBoundary);
@ -96,13 +92,14 @@ public class MimeMultipart extends Multipart {
writer.flush(); writer.flush();
} }
@Override
public InputStream getInputStream() throws MessagingException { public InputStream getInputStream() throws MessagingException {
return null; return null;
} }
@Override @Override
public void setUsing7bitTransport() throws MessagingException { public void setUsing7bitTransport() throws MessagingException {
for (BodyPart part : mParts) { for (BodyPart part : getBodyParts()) {
part.setUsing7bitTransport(); part.setUsing7bitTransport();
} }
} }

View File

@ -927,11 +927,7 @@ public class MimeUtility {
return s.replaceAll("\r|\n", ""); return s.replaceAll("\r|\n", "");
} }
public static String decode(String s) { private static String decode(String s, Message message) {
return decode(s, null);
}
public static String decode(String s, Message message) {
if (s == null) { if (s == null) {
return null; return null;
} }
@ -993,8 +989,7 @@ public class MimeUtility {
throws MessagingException { throws MessagingException {
if (part.getBody() instanceof Multipart) { if (part.getBody() instanceof Multipart) {
Multipart multipart = (Multipart)part.getBody(); Multipart multipart = (Multipart)part.getBody();
for (int i = 0, count = multipart.getCount(); i < count; i++) { for (BodyPart bodyPart : multipart.getBodyParts()) {
BodyPart bodyPart = multipart.getBodyPart(i);
Part ret = findFirstPartByMimeType(bodyPart, mimeType); Part ret = findFirstPartByMimeType(bodyPart, mimeType);
if (ret != null) { if (ret != null) {
return ret; return ret;
@ -1006,28 +1001,6 @@ public class MimeUtility {
return null; return null;
} }
public static Part findPartByContentId(Part part, String contentId) throws Exception {
if (part.getBody() instanceof Multipart) {
Multipart multipart = (Multipart)part.getBody();
for (int i = 0, count = multipart.getCount(); i < count; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
Part ret = findPartByContentId(bodyPart, contentId);
if (ret != null) {
return ret;
}
}
}
String[] header = part.getHeader(MimeHeader.HEADER_CONTENT_ID);
if (header != null) {
for (String s : header) {
if (s.equals(contentId)) {
return part;
}
}
}
return null;
}
/** /**
* Reads the Part's body and returns a String based on any charset conversion that needed * Reads the Part's body and returns a String based on any charset conversion that needed
* to be done. Note, this <b>does not</b> return a text representation of HTML. * to be done. Note, this <b>does not</b> return a text representation of HTML.
@ -1468,7 +1441,7 @@ public class MimeUtility {
* @throws MessagingException * @throws MessagingException
* In case of an error. * In case of an error.
*/ */
public static List<Viewable> getViewables(Part part, List<Part> attachments) throws MessagingException { private static List<Viewable> getViewables(Part part, List<Part> attachments) throws MessagingException {
List<Viewable> viewables = new ArrayList<Viewable>(); List<Viewable> viewables = new ArrayList<Viewable>();
Body body = part.getBody(); Body body = part.getBody();
@ -1490,9 +1463,7 @@ public class MimeUtility {
} }
} else { } else {
// For all other multipart parts we recurse to grab all viewable children. // For all other multipart parts we recurse to grab all viewable children.
int childCount = multipart.getCount(); for (Part bodyPart : multipart.getBodyParts()) {
for (int i = 0; i < childCount; i++) {
Part bodyPart = multipart.getBodyPart(i);
viewables.addAll(getViewables(bodyPart, attachments)); viewables.addAll(getViewables(bodyPart, attachments));
} }
} }
@ -1547,9 +1518,7 @@ public class MimeUtility {
throws MessagingException { throws MessagingException {
List<Viewable> viewables = new ArrayList<Viewable>(); List<Viewable> viewables = new ArrayList<Viewable>();
int childCount = multipart.getCount(); for (Part part : multipart.getBodyParts()) {
for (int i = 0; i < childCount; i++) {
Part part = multipart.getBodyPart(i);
Body body = part.getBody(); Body body = part.getBody();
if (body instanceof Multipart) { if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body; Multipart innerMultipart = (Multipart) body;
@ -1612,9 +1581,7 @@ public class MimeUtility {
List<Viewable> viewables = new ArrayList<Viewable>(); List<Viewable> viewables = new ArrayList<Viewable>();
boolean partFound = false; boolean partFound = false;
int childCount = multipart.getCount(); for (Part part : multipart.getBodyParts()) {
for (int i = 0; i < childCount; i++) {
Part part = multipart.getBodyPart(i);
Body body = part.getBody(); Body body = part.getBody();
if (body instanceof Multipart) { if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body; Multipart innerMultipart = (Multipart) body;
@ -1698,9 +1665,7 @@ public class MimeUtility {
*/ */
private static void findAttachments(Multipart multipart, Set<Part> knownTextParts, private static void findAttachments(Multipart multipart, Set<Part> knownTextParts,
List<Part> attachments) { List<Part> attachments) {
int childCount = multipart.getCount(); for (Part part : multipart.getBodyParts()) {
for (int i = 0; i < childCount; i++) {
Part part = multipart.getBodyPart(i);
Body body = part.getBody(); Body body = part.getBody();
if (body instanceof Multipart) { if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body; Multipart innerMultipart = (Multipart) body;
@ -2049,7 +2014,7 @@ public class MimeUtility {
return null; return null;
} }
public static Boolean isPartTextualBody(Part part) throws MessagingException { private static Boolean isPartTextualBody(Part part) throws MessagingException {
String disposition = part.getDisposition(); String disposition = part.getDisposition();
String dispositionType = null; String dispositionType = null;
String dispositionFilename = null; String dispositionFilename = null;
@ -2137,7 +2102,7 @@ public class MimeUtility {
* *
* @see #MIME_TYPE_REPLACEMENT_MAP * @see #MIME_TYPE_REPLACEMENT_MAP
*/ */
public static String canonicalizeMimeType(String mimeType) { private static String canonicalizeMimeType(String mimeType) {
String lowerCaseMimeType = mimeType.toLowerCase(Locale.US); String lowerCaseMimeType = mimeType.toLowerCase(Locale.US);
for (String[] mimeTypeMapEntry : MIME_TYPE_REPLACEMENT_MAP) { for (String[] mimeTypeMapEntry : MIME_TYPE_REPLACEMENT_MAP) {
if (mimeTypeMapEntry[0].equals(lowerCaseMimeType)) { if (mimeTypeMapEntry[0].equals(lowerCaseMimeType)) {
@ -3422,8 +3387,7 @@ public class MimeUtility {
} else if (part.isMimeType("multipart/alternative") && } else if (part.isMimeType("multipart/alternative") &&
firstBody instanceof MimeMultipart) { firstBody instanceof MimeMultipart) {
MimeMultipart multipart = (MimeMultipart) firstBody; MimeMultipart multipart = (MimeMultipart) firstBody;
for (int i = 0, count = multipart.getCount(); i < count; i++) { for (BodyPart bodyPart : multipart.getBodyParts()) {
BodyPart bodyPart = multipart.getBodyPart(i);
String bodyText = getTextFromPart(bodyPart); String bodyText = getTextFromPart(bodyPart);
if (bodyText != null) { if (bodyText != null) {
if (text.isEmpty() && bodyPart.isMimeType("text/plain")) { if (text.isEmpty() && bodyPart.isMimeType("text/plain")) {

View File

@ -16,7 +16,7 @@ public class TextBody implements Body {
*/ */
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private String mBody; private final String mBody;
private String mEncoding; private String mEncoding;
private String mCharset = "UTF-8"; private String mCharset = "UTF-8";
// Length of the message composed (as opposed to quoted). I don't like the name of this variable and am open to // Length of the message composed (as opposed to quoted). I don't like the name of this variable and am open to
@ -29,6 +29,7 @@ public class TextBody implements Body {
this.mBody = body; this.mBody = body;
} }
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException { public void writeTo(OutputStream out) throws IOException, MessagingException {
if (mBody != null) { if (mBody != null) {
byte[] bytes = mBody.getBytes(mCharset); byte[] bytes = mBody.getBytes(mCharset);
@ -54,6 +55,7 @@ public class TextBody implements Body {
/** /**
* Returns an InputStream that reads this body's text. * Returns an InputStream that reads this body's text.
*/ */
@Override
public InputStream getInputStream() throws MessagingException { public InputStream getInputStream() throws MessagingException {
try { try {
byte[] b; byte[] b;
@ -68,6 +70,7 @@ public class TextBody implements Body {
} }
} }
@Override
public void setEncoding(String encoding) { public void setEncoding(String encoding) {
mEncoding = encoding; mEncoding = encoding;
} }

View File

@ -204,7 +204,10 @@ public class TextBodyBuilder {
return mQuotedTextHtml; return mQuotedTextHtml;
} }
public String textToHtmlFragment(String text) { /**
* protected for unit-test purposes
*/
protected String textToHtmlFragment(String text) {
return HtmlConverter.textToHtmlFragment(text); return HtmlConverter.textToHtmlFragment(text);
} }

View File

@ -7,7 +7,6 @@ import java.io.ByteArrayInputStream;
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.io.UnsupportedEncodingException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -16,8 +15,6 @@ import java.net.SocketAddress;
import java.net.SocketException; import java.net.SocketException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
@ -29,8 +26,11 @@ import java.security.Security;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -64,6 +64,7 @@ import com.fsck.k9.K9;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.controller.MessageRetrievalListener; import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.StringUtils; import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.helper.power.TracingPowerManager; import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock; import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
@ -116,7 +117,7 @@ public class ImapStore extends Store {
private static int FETCH_WINDOW_SIZE = 100; private static int FETCH_WINDOW_SIZE = 100;
private Set<Flag> mPermanentFlagsIndex = new HashSet<Flag>(); private Set<Flag> mPermanentFlagsIndex = EnumSet.noneOf(Flag.class);
private static final String CAPABILITY_IDLE = "IDLE"; private static final String CAPABILITY_IDLE = "IDLE";
private static final String CAPABILITY_AUTH_CRAM_MD5 = "AUTH=CRAM-MD5"; private static final String CAPABILITY_AUTH_CRAM_MD5 = "AUTH=CRAM-MD5";
@ -133,8 +134,6 @@ public class ImapStore extends Store {
private static final String CAPABILITY_COMPRESS_DEFLATE = "COMPRESS=DEFLATE"; private static final String CAPABILITY_COMPRESS_DEFLATE = "COMPRESS=DEFLATE";
private static final String COMMAND_COMPRESS_DEFLATE = "COMPRESS DEFLATE"; private static final String COMMAND_COMPRESS_DEFLATE = "COMPRESS DEFLATE";
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final String[] EMPTY_STRING_ARRAY = new String[0];
/** /**
@ -198,32 +197,27 @@ public class ImapStore extends Store {
} }
if (imapUri.getUserInfo() != null) { if (imapUri.getUserInfo() != null) {
try {
String userinfo = imapUri.getUserInfo(); String userinfo = imapUri.getUserInfo();
String[] userInfoParts = userinfo.split(":"); String[] userInfoParts = userinfo.split(":");
if (userinfo.endsWith(":")) { if (userinfo.endsWith(":")) {
// Password is empty. This can only happen after an account was imported. // Password is empty. This can only happen after an account was imported.
authenticationType = AuthType.valueOf(userInfoParts[0]); authenticationType = AuthType.valueOf(userInfoParts[0]);
username = URLDecoder.decode(userInfoParts[1], "UTF-8"); username = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
} else if (userInfoParts.length == 2) { } else if (userInfoParts.length == 2) {
authenticationType = AuthType.PLAIN; authenticationType = AuthType.PLAIN;
username = URLDecoder.decode(userInfoParts[0], "UTF-8"); username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
password = URLDecoder.decode(userInfoParts[1], "UTF-8"); password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
} else if (userInfoParts.length == 3) { } else if (userInfoParts.length == 3) {
authenticationType = AuthType.valueOf(userInfoParts[0]); authenticationType = AuthType.valueOf(userInfoParts[0]);
username = URLDecoder.decode(userInfoParts[1], "UTF-8"); username = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
if (AuthType.EXTERNAL == authenticationType) { if (AuthType.EXTERNAL == authenticationType) {
clientCertificateAlias = URLDecoder.decode(userInfoParts[2], "UTF-8"); clientCertificateAlias = UrlEncodingHelper.decodeUtf8(userInfoParts[2]);
} else { } else {
password = URLDecoder.decode(userInfoParts[2], "UTF-8"); password = UrlEncodingHelper.decodeUtf8(userInfoParts[2]);
} }
} }
} catch (UnsupportedEncodingException enc) {
// This shouldn't happen since the encoding is hardcoded to UTF-8
throw new IllegalArgumentException("Couldn't urldecode username or password.", enc);
}
} }
String path = imapUri.getPath(); String path = imapUri.getPath();
@ -260,19 +254,11 @@ public class ImapStore extends Store {
* @see ImapStore#decodeUri(String) * @see ImapStore#decodeUri(String)
*/ */
public static String createUri(ServerSettings server) { public static String createUri(ServerSettings server) {
String userEnc; String userEnc = UrlEncodingHelper.encodeUtf8(server.username);
String passwordEnc; String passwordEnc = (server.password != null) ?
String clientCertificateAliasEnc; UrlEncodingHelper.encodeUtf8(server.password) : "";
try { String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
userEnc = URLEncoder.encode(server.username, "UTF-8"); UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
passwordEnc = (server.password != null) ?
URLEncoder.encode(server.password, "UTF-8") : "";
clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
URLEncoder.encode(server.clientCertificateAlias, "UTF-8") : "";
}
catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Could not encode username or password", e);
}
String scheme; String scheme;
switch (server.connectionSecurity) { switch (server.connectionSecurity) {
@ -439,7 +425,7 @@ public class ImapStore extends Store {
private static final SimpleDateFormat RFC3501_DATE = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); private static final SimpleDateFormat RFC3501_DATE = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
private LinkedList<ImapConnection> mConnections = private final Deque<ImapConnection> mConnections =
new LinkedList<ImapConnection>(); new LinkedList<ImapConnection>();
/** /**
@ -453,7 +439,7 @@ public class ImapStore extends Store {
* requests. This cache lets us make sure we always reuse, if possible, for a given * requests. This cache lets us make sure we always reuse, if possible, for a given
* folder name. * folder name.
*/ */
private HashMap<String, ImapFolder> mFolderCache = new HashMap<String, ImapFolder>(); private final Map<String, ImapFolder> mFolderCache = new HashMap<String, ImapFolder>();
public ImapStore(Account account) throws MessagingException { public ImapStore(Account account) throws MessagingException {
super(account); super(account);
@ -756,18 +742,10 @@ public class ImapStore extends Store {
} }
private String encodeFolderName(String name) { private String encodeFolderName(String name) {
try {
ByteBuffer bb = mModifiedUtf7Charset.encode(name); ByteBuffer bb = mModifiedUtf7Charset.encode(name);
byte[] b = new byte[bb.limit()]; byte[] b = new byte[bb.limit()];
bb.get(b); bb.get(b);
return new String(b, "US-ASCII"); return new String(b, Charset.forName("US-ASCII"));
} catch (UnsupportedEncodingException uee) {
/*
* The only thing that can throw this is getBytes("US-ASCII") and if US-ASCII doesn't
* exist we're totally screwed.
*/
throw new RuntimeException("Unable to encode folder name: " + name, uee);
}
} }
private String decodeFolderName(String name) throws CharacterCodingException { private String decodeFolderName(String name) throws CharacterCodingException {
@ -775,18 +753,11 @@ public class ImapStore extends Store {
* Convert the encoded name to US-ASCII, then pass it through the modified UTF-7 * Convert the encoded name to US-ASCII, then pass it through the modified UTF-7
* decoder and return the Unicode String. * decoder and return the Unicode String.
*/ */
try {
// Make sure the decoder throws an exception if it encounters an invalid encoding. // Make sure the decoder throws an exception if it encounters an invalid encoding.
CharsetDecoder decoder = mModifiedUtf7Charset.newDecoder().onMalformedInput(CodingErrorAction.REPORT); CharsetDecoder decoder = mModifiedUtf7Charset.newDecoder().onMalformedInput(CodingErrorAction.REPORT);
CharBuffer cb = decoder.decode(ByteBuffer.wrap(name.getBytes("US-ASCII"))); CharBuffer cb = decoder.decode(ByteBuffer.wrap(name.getBytes(Charset.forName("US-ASCII"))));
return cb.toString(); return cb.toString();
} catch (UnsupportedEncodingException uee) {
/*
* The only thing that can throw this is getBytes("US-ASCII") and if US-ASCII doesn't
* exist we're totally screwed.
*/
throw new RuntimeException("Unable to decode folder name: " + name, uee);
}
} }
@Override @Override
@ -1121,22 +1092,22 @@ public class ImapStore extends Store {
* @return The mapping of original message UIDs to the new server UIDs. * @return The mapping of original message UIDs to the new server UIDs.
*/ */
@Override @Override
public Map<String, String> copyMessages(Message[] messages, Folder folder) public Map<String, String> copyMessages(List<? extends Message> messages, Folder folder)
throws MessagingException { throws MessagingException {
if (!(folder instanceof ImapFolder)) { if (!(folder instanceof ImapFolder)) {
throw new MessagingException("ImapFolder.copyMessages passed non-ImapFolder"); throw new MessagingException("ImapFolder.copyMessages passed non-ImapFolder");
} }
if (messages.length == 0) { if (messages.isEmpty()) {
return null; return null;
} }
ImapFolder iFolder = (ImapFolder)folder; ImapFolder iFolder = (ImapFolder)folder;
checkOpen(); //only need READ access checkOpen(); //only need READ access
String[] uids = new String[messages.length]; String[] uids = new String[messages.size()];
for (int i = 0, count = messages.length; i < count; i++) { for (int i = 0, count = messages.size(); i < count; i++) {
uids[i] = messages[i].getUid(); uids[i] = messages.get(i).getUid();
} }
try { try {
@ -1222,21 +1193,21 @@ public class ImapStore extends Store {
} }
@Override @Override
public Map<String, String> moveMessages(Message[] messages, Folder folder) throws MessagingException { public Map<String, String> moveMessages(List<? extends Message> messages, Folder folder) throws MessagingException {
if (messages.length == 0) if (messages.isEmpty())
return null; return null;
Map<String, String> uidMap = copyMessages(messages, folder); Map<String, String> uidMap = copyMessages(messages, folder);
setFlags(messages, new Flag[] { Flag.DELETED }, true); setFlags(messages, Collections.singleton(Flag.DELETED), true);
return uidMap; return uidMap;
} }
@Override @Override
public void delete(Message[] messages, String trashFolderName) throws MessagingException { public void delete(List<? extends Message> messages, String trashFolderName) throws MessagingException {
if (messages.length == 0) if (messages.isEmpty())
return; return;
if (trashFolderName == null || getName().equalsIgnoreCase(trashFolderName)) { if (trashFolderName == null || getName().equalsIgnoreCase(trashFolderName)) {
setFlags(messages, new Flag[] { Flag.DELETED }, true); setFlags(messages, Collections.singleton(Flag.DELETED), true);
} else { } else {
ImapFolder remoteTrashFolder = (ImapFolder)getStore().getFolder(trashFolderName); ImapFolder remoteTrashFolder = (ImapFolder)getStore().getFolder(trashFolderName);
String remoteTrashName = encodeString(encodeFolderName(remoteTrashFolder.getPrefixedName())); String remoteTrashName = encodeString(encodeFolderName(remoteTrashFolder.getPrefixedName()));
@ -1252,7 +1223,7 @@ public class ImapStore extends Store {
if (exists(remoteTrashName)) { if (exists(remoteTrashName)) {
if (K9.DEBUG) if (K9.DEBUG)
Log.d(K9.LOG_TAG, "IMAPMessage.delete: copying remote " + messages.length + " messages to '" + trashFolderName + "' for " + getLogId()); Log.d(K9.LOG_TAG, "IMAPMessage.delete: copying remote " + messages.size() + " messages to '" + trashFolderName + "' for " + getLogId());
moveMessages(messages, remoteTrashFolder); moveMessages(messages, remoteTrashFolder);
} else { } else {
@ -1302,13 +1273,14 @@ public class ImapStore extends Store {
protected long getHighestUid() { protected long getHighestUid() {
try { try {
ImapSearcher searcher = new ImapSearcher() { ImapSearcher searcher = new ImapSearcher() {
@Override
public List<ImapResponse> search() throws IOException, MessagingException { public List<ImapResponse> search() throws IOException, MessagingException {
return executeSimpleCommand("UID SEARCH *:*"); return executeSimpleCommand("UID SEARCH *:*");
} }
}; };
Message[] messages = search(searcher, null).toArray(EMPTY_MESSAGE_ARRAY); List<? extends Message> messages = search(searcher, null);
if (messages.length > 0) { if (messages.size() > 0) {
return Long.parseLong(messages[0].getUid()); return Long.parseLong(messages.get(0).getUid());
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(K9.LOG_TAG, "Unable to find highest UID in folder " + getName(), e); Log.e(K9.LOG_TAG, "Unable to find highest UID in folder " + getName(), e);
@ -1329,12 +1301,12 @@ public class ImapStore extends Store {
@Override @Override
public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) public List<? extends Message> getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
return getMessages(start, end, earliestDate, false, listener); return getMessages(start, end, earliestDate, false, listener);
} }
protected Message[] getMessages(final int start, final int end, Date earliestDate, final boolean includeDeleted, final MessageRetrievalListener listener) protected List<? extends Message> getMessages(final int start, final int end, Date earliestDate, final boolean includeDeleted, final MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
if (start < 1 || end < 1 || end < start) { if (start < 1 || end < 1 || end < start) {
throw new MessagingException( throw new MessagingException(
@ -1351,39 +1323,42 @@ public class ImapStore extends Store {
ImapSearcher searcher = new ImapSearcher() { ImapSearcher searcher = new ImapSearcher() {
@Override
public List<ImapResponse> search() throws IOException, MessagingException { public List<ImapResponse> search() throws IOException, MessagingException {
return executeSimpleCommand(String.format(Locale.US, "UID SEARCH %d:%d%s%s", start, end, dateSearchString, includeDeleted ? "" : " NOT DELETED")); return executeSimpleCommand(String.format(Locale.US, "UID SEARCH %d:%d%s%s", start, end, dateSearchString, includeDeleted ? "" : " NOT DELETED"));
} }
}; };
return search(searcher, listener).toArray(EMPTY_MESSAGE_ARRAY); return search(searcher, listener);
} }
protected Message[] getMessages(final List<Long> mesgSeqs, final boolean includeDeleted, final MessageRetrievalListener listener) protected List<? extends Message> getMessages(final List<Long> mesgSeqs, final boolean includeDeleted, final MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
ImapSearcher searcher = new ImapSearcher() { ImapSearcher searcher = new ImapSearcher() {
@Override
public List<ImapResponse> search() throws IOException, MessagingException { public List<ImapResponse> search() throws IOException, MessagingException {
return executeSimpleCommand(String.format("UID SEARCH %s%s", Utility.combine(mesgSeqs.toArray(), ','), includeDeleted ? "" : " NOT DELETED")); return executeSimpleCommand(String.format("UID SEARCH %s%s", Utility.combine(mesgSeqs.toArray(), ','), includeDeleted ? "" : " NOT DELETED"));
} }
}; };
return search(searcher, listener).toArray(EMPTY_MESSAGE_ARRAY); return search(searcher, listener);
} }
protected Message[] getMessagesFromUids(final List<String> mesgUids, final boolean includeDeleted, final MessageRetrievalListener listener) protected List<? extends Message> getMessagesFromUids(final List<String> mesgUids, final boolean includeDeleted, final MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
ImapSearcher searcher = new ImapSearcher() { ImapSearcher searcher = new ImapSearcher() {
@Override
public List<ImapResponse> search() throws IOException, MessagingException { public List<ImapResponse> search() throws IOException, MessagingException {
return executeSimpleCommand(String.format("UID SEARCH UID %s%s", Utility.combine(mesgUids.toArray(), ','), includeDeleted ? "" : " NOT DELETED")); return executeSimpleCommand(String.format("UID SEARCH UID %s%s", Utility.combine(mesgUids.toArray(), ','), includeDeleted ? "" : " NOT DELETED"));
} }
}; };
return search(searcher, listener).toArray(EMPTY_MESSAGE_ARRAY); return search(searcher, listener);
} }
private List<Message> search(ImapSearcher searcher, MessageRetrievalListener listener) throws MessagingException { private List<Message> search(ImapSearcher searcher, MessageRetrievalListener listener) throws MessagingException {
checkOpen(); //only need READ access checkOpen(); //only need READ access
ArrayList<Message> messages = new ArrayList<Message>(); List<Message> messages = new ArrayList<Message>();
try { try {
ArrayList<Long> uids = new ArrayList<Long>(); List<Long> uids = new ArrayList<Long>();
List<ImapResponse> responses = searcher.search(); // List<ImapResponse> responses = searcher.search(); //
for (ImapResponse response : responses) { for (ImapResponse response : responses) {
if (response.mTag == null) { if (response.mTag == null) {
@ -1420,19 +1395,19 @@ public class ImapStore extends Store {
@Override @Override
public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException { public List<? extends Message> getMessages(MessageRetrievalListener listener) throws MessagingException {
return getMessages(null, listener); return getMessages(null, listener);
} }
@Override @Override
public Message[] getMessages(String[] uids, MessageRetrievalListener listener) public List<? extends Message> getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
checkOpen(); //only need READ access checkOpen(); //only need READ access
ArrayList<Message> messages = new ArrayList<Message>(); List<Message> messages = new ArrayList<Message>();
try { try {
if (uids == null) { if (uids == null) {
List<ImapResponse> responses = executeSimpleCommand("UID SEARCH 1:* NOT DELETED"); List<ImapResponse> responses = executeSimpleCommand("UID SEARCH 1:* NOT DELETED");
ArrayList<String> tempUids = new ArrayList<String>(); List<String> tempUids = new ArrayList<String>();
for (ImapResponse response : responses) { for (ImapResponse response : responses) {
if (ImapResponseParser.equalsIgnoreCase(response.get(0), "SEARCH")) { if (ImapResponseParser.equalsIgnoreCase(response.get(0), "SEARCH")) {
for (int i = 1, count = response.size(); i < count; i++) { for (int i = 1, count = response.size(); i < count; i++) {
@ -1455,17 +1430,17 @@ public class ImapStore extends Store {
} catch (IOException ioe) { } catch (IOException ioe) {
throw ioExceptionHandler(mConnection, ioe); throw ioExceptionHandler(mConnection, ioe);
} }
return messages.toArray(EMPTY_MESSAGE_ARRAY); return messages;
} }
@Override @Override
public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener) public void fetch(List<? extends Message> messages, FetchProfile fp, MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
if (messages == null || messages.length == 0) { if (messages == null || messages.isEmpty()) {
return; return;
} }
checkOpen(); //only need READ access checkOpen(); //only need READ access
List<String> uids = new ArrayList<String>(messages.length); List<String> uids = new ArrayList<String>(messages.size());
HashMap<String, Message> messageMap = new HashMap<String, Message>(); HashMap<String, Message> messageMap = new HashMap<String, Message>();
for (Message msg : messages) { for (Message msg : messages) {
String uid = msg.getUid(); String uid = msg.getUid();
@ -1507,8 +1482,8 @@ public class ImapStore extends Store {
for (int windowStart = 0; windowStart < messages.length; windowStart += (FETCH_WINDOW_SIZE)) { for (int windowStart = 0; windowStart < messages.size(); windowStart += (FETCH_WINDOW_SIZE)) {
List<String> uidWindow = uids.subList(windowStart, Math.min((windowStart + FETCH_WINDOW_SIZE), messages.length)); List<String> uidWindow = uids.subList(windowStart, Math.min((windowStart + FETCH_WINDOW_SIZE), messages.size()));
try { try {
mConnection.sendCommand(String.format("UID FETCH %s (%s)", mConnection.sendCommand(String.format("UID FETCH %s (%s)",
@ -1834,7 +1809,7 @@ public class ImapStore extends Store {
* For each part in the message we're going to add a new BodyPart and parse * For each part in the message we're going to add a new BodyPart and parse
* into it. * into it.
*/ */
ImapBodyPart bp = new ImapBodyPart(); MimeBodyPart bp = new MimeBodyPart();
if (id.equalsIgnoreCase("TEXT")) { if (id.equalsIgnoreCase("TEXT")) {
parseBodyStructure(bs.getList(i), bp, Integer.toString(i + 1)); parseBodyStructure(bs.getList(i), bp, Integer.toString(i + 1));
} else { } else {
@ -1970,10 +1945,6 @@ public class ImapStore extends Store {
if (part instanceof ImapMessage) { if (part instanceof ImapMessage) {
((ImapMessage) part).setSize(size); ((ImapMessage) part).setSize(size);
} else if (part instanceof ImapBodyPart) {
((ImapBodyPart) part).setSize(size);
} else {
throw new MessagingException("Unknown part type " + part.toString());
} }
part.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, id); part.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, id);
} }
@ -1994,7 +1965,7 @@ public class ImapStore extends Store {
* @return The mapping of original message UIDs to the new server UIDs. * @return The mapping of original message UIDs to the new server UIDs.
*/ */
@Override @Override
public Map<String, String> appendMessages(Message[] messages) throws MessagingException { public Map<String, String> appendMessages(List<? extends Message> messages) throws MessagingException {
open(OPEN_MODE_RW); open(OPEN_MODE_RW);
checkOpen(); checkOpen();
try { try {
@ -2066,7 +2037,7 @@ public class ImapStore extends Store {
* with the behavior of other similar methods (copyMessages, moveMessages) which * with the behavior of other similar methods (copyMessages, moveMessages) which
* return null. * return null.
*/ */
return (uidMap.size() == 0) ? null : uidMap; return (uidMap.isEmpty()) ? null : uidMap;
} catch (IOException ioe) { } catch (IOException ioe) {
throw ioExceptionHandler(mConnection, ioe); throw ioExceptionHandler(mConnection, ioe);
} }
@ -2117,8 +2088,8 @@ public class ImapStore extends Store {
} }
} }
private String combineFlags(Flag[] flags) { private String combineFlags(Iterable<Flag> flags) {
ArrayList<String> flagNames = new ArrayList<String>(); List<String> flagNames = new ArrayList<String>();
for (Flag flag : flags) { for (Flag flag : flags) {
if (flag == Flag.SEEN) { if (flag == Flag.SEEN) {
flagNames.add("\\Seen"); flagNames.add("\\Seen");
@ -2139,7 +2110,7 @@ public class ImapStore extends Store {
@Override @Override
public void setFlags(Flag[] flags, boolean value) public void setFlags(Set<Flag> flags, boolean value)
throws MessagingException { throws MessagingException {
open(OPEN_MODE_RW); open(OPEN_MODE_RW);
checkOpen(); checkOpen();
@ -2174,13 +2145,13 @@ public class ImapStore extends Store {
@Override @Override
public void setFlags(Message[] messages, Flag[] flags, boolean value) public void setFlags(List<? extends Message> messages, final Set<Flag> flags, boolean value)
throws MessagingException { throws MessagingException {
open(OPEN_MODE_RW); open(OPEN_MODE_RW);
checkOpen(); checkOpen();
String[] uids = new String[messages.length]; String[] uids = new String[messages.size()];
for (int i = 0, count = messages.length; i < count; i++) { for (int i = 0, count = messages.size(); i < count; i++) {
uids[i] = messages[i].getUid(); uids[i] = messages.get(i).getUid();
} }
try { try {
executeSimpleCommand(String.format("UID STORE %s %sFLAGS.SILENT (%s)", executeSimpleCommand(String.format("UID STORE %s %sFLAGS.SILENT (%s)",
@ -2241,7 +2212,7 @@ public class ImapStore extends Store {
* @throws MessagingException On any error. * @throws MessagingException On any error.
*/ */
@Override @Override
public List<Message> search(final String queryString, final Flag[] requiredFlags, final Flag[] forbiddenFlags) public List<Message> search(final String queryString, final Set<Flag> requiredFlags, final Set<Flag> forbiddenFlags)
throws MessagingException { throws MessagingException {
if (!mAccount.allowRemoteSearch()) { if (!mAccount.allowRemoteSearch()) {
@ -2250,6 +2221,7 @@ public class ImapStore extends Store {
// Setup the searcher // Setup the searcher
final ImapSearcher searcher = new ImapSearcher() { final ImapSearcher searcher = new ImapSearcher() {
@Override
public List<ImapResponse> search() throws IOException, MessagingException { public List<ImapResponse> search() throws IOException, MessagingException {
String imapQuery = "UID SEARCH "; String imapQuery = "UID SEARCH ";
if (requiredFlags != null) { if (requiredFlags != null) {
@ -2345,12 +2317,12 @@ public class ImapStore extends Store {
* A cacheable class that stores the details for a single IMAP connection. * A cacheable class that stores the details for a single IMAP connection.
*/ */
public static class ImapConnection { public static class ImapConnection {
protected Socket mSocket; private Socket mSocket;
protected PeekableInputStream mIn; private PeekableInputStream mIn;
protected OutputStream mOut; private OutputStream mOut;
protected ImapResponseParser mParser; private ImapResponseParser mParser;
protected int mNextCommandTag; private int mNextCommandTag;
protected Set<String> capabilities = new HashSet<String>(); private Set<String> capabilities = new HashSet<String>();
private ImapSettings mSettings; private ImapSettings mSettings;
@ -2761,10 +2733,10 @@ public class ImapStore extends Store {
return response; return response;
} }
protected ArrayList<ImapResponse> readStatusResponse(String tag, protected List<ImapResponse> readStatusResponse(String tag,
String commandToLog, UntaggedHandler untaggedHandler) String commandToLog, UntaggedHandler untaggedHandler)
throws IOException, MessagingException { throws IOException, MessagingException {
ArrayList<ImapResponse> responses = new ArrayList<ImapResponse>(); List<ImapResponse> responses = new ArrayList<ImapResponse>();
ImapResponse response; ImapResponse response;
do { do {
response = mParser.readResponse(); response = mParser.readResponse();
@ -2931,11 +2903,6 @@ public class ImapStore extends Store {
this.mSize = size; this.mSize = size;
} }
@Override
public void parse(InputStream in) throws IOException, MessagingException {
super.parse(in);
}
public void setFlagInternal(Flag flag, boolean set) throws MessagingException { public void setFlagInternal(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set); super.setFlag(flag, set);
} }
@ -2944,28 +2911,18 @@ public class ImapStore extends Store {
@Override @Override
public void setFlag(Flag flag, boolean set) throws MessagingException { public void setFlag(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set); super.setFlag(flag, set);
mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set); mFolder.setFlags(Collections.singletonList(this), Collections.singleton(flag), set);
} }
@Override @Override
public void delete(String trashFolderName) throws MessagingException { public void delete(String trashFolderName) throws MessagingException {
getFolder().delete(new Message[] { this }, trashFolderName); getFolder().delete(Collections.singletonList(this), trashFolderName);
}
}
static class ImapBodyPart extends MimeBodyPart {
public ImapBodyPart() throws MessagingException {
super();
}
public void setSize(int size) {
this.mSize = size;
} }
} }
static class ImapException extends MessagingException { static class ImapException extends MessagingException {
private static final long serialVersionUID = 3725007182205882394L; private static final long serialVersionUID = 3725007182205882394L;
String mAlertText; private final String mAlertText;
public ImapException(String message, String alertText) { public ImapException(String message, String alertText) {
super(message, true); super(message, true);
@ -2976,22 +2933,19 @@ public class ImapStore extends Store {
return mAlertText; return mAlertText;
} }
public void setAlertText(String alertText) {
mAlertText = alertText;
}
} }
public class ImapFolderPusher extends ImapFolder implements UntaggedHandler { public class ImapFolderPusher extends ImapFolder implements UntaggedHandler {
final PushReceiver receiver; private final PushReceiver receiver;
Thread listeningThread = null; private Thread listeningThread = null;
final AtomicBoolean stop = new AtomicBoolean(false); private final AtomicBoolean stop = new AtomicBoolean(false);
final AtomicBoolean idling = new AtomicBoolean(false); private final AtomicBoolean idling = new AtomicBoolean(false);
final AtomicBoolean doneSent = new AtomicBoolean(false); private final AtomicBoolean doneSent = new AtomicBoolean(false);
final AtomicInteger delayTime = new AtomicInteger(NORMAL_DELAY_TIME); private final AtomicInteger delayTime = new AtomicInteger(NORMAL_DELAY_TIME);
final AtomicInteger idleFailureCount = new AtomicInteger(0); private final AtomicInteger idleFailureCount = new AtomicInteger(0);
final AtomicBoolean needsPoll = new AtomicBoolean(false); private final AtomicBoolean needsPoll = new AtomicBoolean(false);
List<ImapResponse> storedUntaggedResponses = new ArrayList<ImapResponse>(); private List<ImapResponse> storedUntaggedResponses = new ArrayList<ImapResponse>();
TracingWakeLock wakeLock = null; private TracingWakeLock wakeLock = null;
public ImapFolderPusher(ImapStore store, String name, PushReceiver nReceiver) { public ImapFolderPusher(ImapStore store, String name, PushReceiver nReceiver) {
super(store, name); super(store, name);
@ -3029,6 +2983,7 @@ public class ImapStore extends Store {
public void start() { public void start() {
Runnable runner = new Runnable() { Runnable runner = new Runnable() {
@Override
public void run() { public void run() {
wakeLock.acquire(K9.PUSH_WAKE_LOCK_TIMEOUT); wakeLock.acquire(K9.PUSH_WAKE_LOCK_TIMEOUT);
if (K9.DEBUG) if (K9.DEBUG)
@ -3254,9 +3209,9 @@ public class ImapStore extends Store {
Log.e(K9.LOG_TAG, "Unable to get oldUidNext for " + getLogId(), e); Log.e(K9.LOG_TAG, "Unable to get oldUidNext for " + getLogId(), e);
} }
Message[] messageArray = getMessages(end, end, null, true, null); List<? extends Message> messageList = getMessages(end, end, null, true, null);
if (messageArray != null && messageArray.length > 0) { if (messageList != null && messageList.size() > 0) {
long newUid = Long.parseLong(messageArray[0].getUid()); long newUid = Long.parseLong(messageList.get(0).getUid());
if (K9.DEBUG) if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Got newUid " + newUid + " for message " + end + " on " + getLogId()); Log.i(K9.LOG_TAG, "Got newUid " + newUid + " for message " + end + " on " + getLogId());
long startUid = oldUidNext; long startUid = oldUidNext;
@ -3284,12 +3239,10 @@ public class ImapStore extends Store {
private void syncMessages(List<Long> flagSyncMsgSeqs) { private void syncMessages(List<Long> flagSyncMsgSeqs) {
try { try {
Message[] messageArray = null; List<? extends Message> messageList = getMessages(flagSyncMsgSeqs, true, null);
messageArray = getMessages(flagSyncMsgSeqs, true, null);
List<Message> messages = new ArrayList<Message>(); List<Message> messages = new ArrayList<Message>();
messages.addAll(Arrays.asList(messageArray)); messages.addAll(messageList);
pushMessages(messages, false); pushMessages(messages, false);
} catch (Exception e) { } catch (Exception e) {
@ -3301,7 +3254,7 @@ public class ImapStore extends Store {
List<Message> messages = new ArrayList<Message>(removeUids.size()); List<Message> messages = new ArrayList<Message>(removeUids.size());
try { try {
Message[] existingMessages = getMessagesFromUids(removeUids, true, null); List<? extends Message> existingMessages = getMessagesFromUids(removeUids, true, null);
for (Message existingMessage : existingMessages) { for (Message existingMessage : existingMessages) {
needsPoll.set(true); needsPoll.set(true);
msgSeqUidMap.clear(); msgSeqUidMap.clear();
@ -3428,6 +3381,7 @@ public class ImapStore extends Store {
} }
} }
@Override
public void handleAsyncUntaggedResponse(ImapResponse response) { public void handleAsyncUntaggedResponse(ImapResponse response) {
if (K9.DEBUG) if (K9.DEBUG)
Log.v(K9.LOG_TAG, "Got async response: " + response); Log.v(K9.LOG_TAG, "Got async response: " + response);
@ -3478,17 +3432,18 @@ public class ImapStore extends Store {
} }
public class ImapPusher implements Pusher { public class ImapPusher implements Pusher {
final ImapStore mStore; private final ImapStore mStore;
final PushReceiver mReceiver; final PushReceiver mReceiver;
private long lastRefresh = -1; private long lastRefresh = -1;
HashMap<String, ImapFolderPusher> folderPushers = new HashMap<String, ImapFolderPusher>(); final Map<String, ImapFolderPusher> folderPushers = new HashMap<String, ImapFolderPusher>();
public ImapPusher(ImapStore store, PushReceiver receiver) { public ImapPusher(ImapStore store, PushReceiver receiver) {
mStore = store; mStore = store;
mReceiver = receiver; mReceiver = receiver;
} }
@Override
public void start(List<String> folderNames) { public void start(List<String> folderNames) {
stop(); stop();
synchronized (folderPushers) { synchronized (folderPushers) {
@ -3504,6 +3459,7 @@ public class ImapStore extends Store {
} }
} }
@Override
public void refresh() { public void refresh() {
synchronized (folderPushers) { synchronized (folderPushers) {
for (ImapFolderPusher folderPusher : folderPushers.values()) { for (ImapFolderPusher folderPusher : folderPushers.values()) {
@ -3516,6 +3472,7 @@ public class ImapStore extends Store {
} }
} }
@Override
public void stop() { public void stop() {
if (K9.DEBUG) if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Requested stop of IMAP pusher"); Log.i(K9.LOG_TAG, "Requested stop of IMAP pusher");
@ -3534,14 +3491,17 @@ public class ImapStore extends Store {
} }
} }
@Override
public int getRefreshInterval() { public int getRefreshInterval() {
return (getAccount().getIdleRefreshMinutes() * 60 * 1000); return (getAccount().getIdleRefreshMinutes() * 60 * 1000);
} }
@Override
public long getLastRefresh() { public long getLastRefresh() {
return lastRefresh; return lastRefresh;
} }
@Override
public void setLastRefresh(long lastRefresh) { public void setLastRefresh(long lastRefresh) {
this.lastRefresh = lastRefresh; this.lastRefresh = lastRefresh;
} }
@ -3590,9 +3550,9 @@ public class ImapStore extends Store {
} }
private static class FetchBodyCallback implements ImapResponseParser.IImapResponseCallback { private static class FetchBodyCallback implements ImapResponseParser.IImapResponseCallback {
private HashMap<String, Message> mMessageMap; private Map<String, Message> mMessageMap;
FetchBodyCallback(HashMap<String, Message> mesageMap) { FetchBodyCallback(Map<String, Message> mesageMap) {
mMessageMap = mesageMap; mMessageMap = mesageMap;
} }

File diff suppressed because it is too large Load Diff

View File

@ -99,7 +99,7 @@ public class LockableDatabase {
} }
try { try {
openOrCreateDataspace(mApplication); openOrCreateDataspace();
} catch (UnavailableStorageException e) { } catch (UnavailableStorageException e) {
Log.e(K9.LOG_TAG, "Unable to open DB on mount", e); Log.e(K9.LOG_TAG, "Unable to open DB on mount", e);
} }
@ -346,7 +346,7 @@ public class LockableDatabase {
mStorageProviderId = newProviderId; mStorageProviderId = newProviderId;
// re-initialize this class with the new Uri // re-initialize this class with the new Uri
openOrCreateDataspace(mApplication); openOrCreateDataspace();
} finally { } finally {
unlockWrite(newProviderId); unlockWrite(newProviderId);
} }
@ -358,7 +358,7 @@ public class LockableDatabase {
public void open() throws UnavailableStorageException { public void open() throws UnavailableStorageException {
lockWrite(); lockWrite();
try { try {
openOrCreateDataspace(mApplication); openOrCreateDataspace();
} finally { } finally {
unlockWrite(); unlockWrite();
} }
@ -367,33 +367,20 @@ public class LockableDatabase {
/** /**
* *
* @param application
* @throws UnavailableStorageException * @throws UnavailableStorageException
*/ */
protected void openOrCreateDataspace(final Application application) throws UnavailableStorageException { private void openOrCreateDataspace() throws UnavailableStorageException {
lockWrite(); lockWrite();
try { try {
final File databaseFile = prepareStorage(mStorageProviderId); final File databaseFile = prepareStorage(mStorageProviderId);
try { try {
if (StorageManager.InternalStorageProvider.ID.equals(mStorageProviderId)) { doOpenOrCreateDb(databaseFile);
// internal storage
mDb = application.openOrCreateDatabase(databaseFile.getName(), Context.MODE_PRIVATE, null);
} else {
// external storage
mDb = SQLiteDatabase.openOrCreateDatabase(databaseFile, null);
}
} catch (SQLiteException e) { } catch (SQLiteException e) {
// try to gracefully handle DB corruption - see issue 2537 // try to gracefully handle DB corruption - see issue 2537
Log.w(K9.LOG_TAG, "Unable to open DB " + databaseFile + " - removing file and retrying", e); Log.w(K9.LOG_TAG, "Unable to open DB " + databaseFile + " - removing file and retrying", e);
databaseFile.delete(); databaseFile.delete();
if (StorageManager.InternalStorageProvider.ID.equals(mStorageProviderId)) { doOpenOrCreateDb(databaseFile);
// internal storage
mDb = application.openOrCreateDatabase(databaseFile.getName(), Context.MODE_PRIVATE, null);
} else {
// external storage
mDb = SQLiteDatabase.openOrCreateDatabase(databaseFile, null);
}
} }
if (mDb.getVersion() != mSchemaDefinition.getVersion()) { if (mDb.getVersion() != mSchemaDefinition.getVersion()) {
mSchemaDefinition.doDbUpgrade(mDb); mSchemaDefinition.doDbUpgrade(mDb);
@ -403,6 +390,17 @@ public class LockableDatabase {
} }
} }
private void doOpenOrCreateDb(final File databaseFile) {
if (StorageManager.InternalStorageProvider.ID.equals(mStorageProviderId)) {
// internal storage
mDb = mApplication.openOrCreateDatabase(databaseFile.getName(), Context.MODE_PRIVATE,
null);
} else {
// external storage
mDb = SQLiteDatabase.openOrCreateDatabase(databaseFile, null);
}
}
/** /**
* @param providerId * @param providerId
* Never <code>null</code>. * Never <code>null</code>.
@ -412,10 +410,8 @@ public class LockableDatabase {
protected File prepareStorage(final String providerId) throws UnavailableStorageException { protected File prepareStorage(final String providerId) throws UnavailableStorageException {
final StorageManager storageManager = getStorageManager(); final StorageManager storageManager = getStorageManager();
final File databaseFile; final File databaseFile = storageManager.getDatabase(uUid, providerId);
final File databaseParentDir; final File databaseParentDir = databaseFile.getParentFile();
databaseFile = storageManager.getDatabase(uUid, providerId);
databaseParentDir = databaseFile.getParentFile();
if (databaseParentDir.isFile()) { if (databaseParentDir.isFile()) {
// should be safe to unconditionally delete clashing file: user is not supposed to mess with our directory // should be safe to unconditionally delete clashing file: user is not supposed to mess with our directory
databaseParentDir.delete(); databaseParentDir.delete();
@ -428,11 +424,8 @@ public class LockableDatabase {
Utility.touchFile(databaseParentDir, ".nomedia"); Utility.touchFile(databaseParentDir, ".nomedia");
} }
final File attachmentDir; final File attachmentDir = storageManager.getAttachmentDirectory(uUid, providerId);
final File attachmentParentDir; final File attachmentParentDir = attachmentDir.getParentFile();
attachmentDir = storageManager
.getAttachmentDirectory(uUid, providerId);
attachmentParentDir = attachmentDir.getParentFile();
if (!attachmentParentDir.exists()) { if (!attachmentParentDir.exists()) {
attachmentParentDir.mkdirs(); attachmentParentDir.mkdirs();
Utility.touchFile(attachmentParentDir, ".nomedia"); Utility.touchFile(attachmentParentDir, ".nomedia");
@ -467,7 +460,8 @@ public class LockableDatabase {
try { try {
mDb.close(); mDb.close();
} catch (Exception e) { } catch (Exception e) {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Exception caught in DB close: " + e.getMessage());
} }
final StorageManager storageManager = getStorageManager(); final StorageManager storageManager = getStorageManager();
try { try {
@ -482,6 +476,8 @@ public class LockableDatabase {
attachmentDirectory.delete(); attachmentDirectory.delete();
} }
} catch (Exception e) { } catch (Exception e) {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Exception caught in clearing attachments: " + e.getMessage());
} }
try { try {
deleteDatabase(storageManager.getDatabase(uUid, mStorageProviderId)); deleteDatabase(storageManager.getDatabase(uUid, mStorageProviderId));
@ -490,7 +486,7 @@ public class LockableDatabase {
} }
if (recreate) { if (recreate) {
openOrCreateDataspace(mApplication); openOrCreateDataspace();
} else { } else {
// stop waiting for mount/unmount events // stop waiting for mount/unmount events
getStorageManager().removeListener(mStorageListener); getStorageManager().removeListener(mStorageListener);

View File

@ -7,6 +7,7 @@ import com.fsck.k9.Account;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.controller.MessageRetrievalListener; import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*; import com.fsck.k9.mail.*;
import com.fsck.k9.mail.filter.Base64; import com.fsck.k9.mail.filter.Base64;
@ -23,6 +24,8 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.HashMap; import java.util.HashMap;
@ -115,7 +118,6 @@ public class Pop3Store extends Store {
AuthType authType = AuthType.PLAIN; AuthType authType = AuthType.PLAIN;
if (pop3Uri.getUserInfo() != null) { if (pop3Uri.getUserInfo() != null) {
try {
int userIndex = 0, passwordIndex = 1; int userIndex = 0, passwordIndex = 1;
String userinfo = pop3Uri.getUserInfo(); String userinfo = pop3Uri.getUserInfo();
String[] userInfoParts = userinfo.split(":"); String[] userInfoParts = userinfo.split(":");
@ -126,18 +128,14 @@ public class Pop3Store extends Store {
passwordIndex++; passwordIndex++;
authType = AuthType.valueOf(userInfoParts[0]); authType = AuthType.valueOf(userInfoParts[0]);
} }
username = URLDecoder.decode(userInfoParts[userIndex], "UTF-8"); username = UrlEncodingHelper.decodeUtf8(userInfoParts[userIndex]);
if (userInfoParts.length > passwordIndex) { if (userInfoParts.length > passwordIndex) {
if (authType == AuthType.EXTERNAL) { if (authType == AuthType.EXTERNAL) {
clientCertificateAlias = URLDecoder.decode(userInfoParts[passwordIndex], "UTF-8"); clientCertificateAlias = UrlEncodingHelper.decodeUtf8(userInfoParts[passwordIndex]);
} else { } else {
password = URLDecoder.decode(userInfoParts[passwordIndex], "UTF-8"); password = UrlEncodingHelper.decodeUtf8(userInfoParts[passwordIndex]);
} }
} }
} catch (UnsupportedEncodingException enc) {
// This shouldn't happen since the encoding is hardcoded to UTF-8
throw new IllegalArgumentException("Couldn't urldecode username or password.", enc);
}
} }
return new ServerSettings(STORE_TYPE, host, port, connectionSecurity, authType, username, return new ServerSettings(STORE_TYPE, host, port, connectionSecurity, authType, username,
@ -156,19 +154,11 @@ public class Pop3Store extends Store {
* @see Pop3Store#decodeUri(String) * @see Pop3Store#decodeUri(String)
*/ */
public static String createUri(ServerSettings server) { public static String createUri(ServerSettings server) {
String userEnc; String userEnc = UrlEncodingHelper.encodeUtf8(server.username);
String passwordEnc; String passwordEnc = (server.password != null) ?
String clientCertificateAliasEnc; UrlEncodingHelper.encodeUtf8(server.password) : "";
try { String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
userEnc = URLEncoder.encode(server.username, "UTF-8"); UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
passwordEnc = (server.password != null) ?
URLEncoder.encode(server.password, "UTF-8") : "";
clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
URLEncoder.encode(server.clientCertificateAlias, "UTF-8") : "";
}
catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Could not encode username or password", e);
}
String scheme; String scheme;
switch (server.connectionSecurity) { switch (server.connectionSecurity) {
@ -208,7 +198,7 @@ public class Pop3Store extends Store {
private String mClientCertificateAlias; private String mClientCertificateAlias;
private AuthType mAuthType; private AuthType mAuthType;
private ConnectionSecurity mConnectionSecurity; private ConnectionSecurity mConnectionSecurity;
private HashMap<String, Folder> mFolders = new HashMap<String, Folder>(); private Map<String, Folder> mFolders = new HashMap<String, Folder>();
private Pop3Capabilities mCapabilities; private Pop3Capabilities mCapabilities;
/** /**
@ -286,9 +276,9 @@ public class Pop3Store extends Store {
private Socket mSocket; private Socket mSocket;
private InputStream mIn; private InputStream mIn;
private OutputStream mOut; private OutputStream mOut;
private HashMap<String, Pop3Message> mUidToMsgMap = new HashMap<String, Pop3Message>(); private Map<String, Pop3Message> mUidToMsgMap = new HashMap<String, Pop3Message>();
private HashMap<Integer, Pop3Message> mMsgNumToMsgMap = new HashMap<Integer, Pop3Message>(); private Map<Integer, Pop3Message> mMsgNumToMsgMap = new HashMap<Integer, Pop3Message>();
private HashMap<String, Integer> mUidToMsgNumMap = new HashMap<String, Integer>(); private Map<String, Integer> mUidToMsgNumMap = new HashMap<String, Integer>();
private String mName; private String mName;
private int mMessageCount; private int mMessageCount;
@ -578,7 +568,7 @@ public class Pop3Store extends Store {
} }
@Override @Override
public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) public List<? extends Message> getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
if (start < 1 || end < 1 || end < start) { if (start < 1 || end < 1 || end < start) {
throw new MessagingException(String.format(Locale.US, "Invalid message set %d %d", throw new MessagingException(String.format(Locale.US, "Invalid message set %d %d",
@ -589,7 +579,7 @@ public class Pop3Store extends Store {
} catch (IOException ioe) { } catch (IOException ioe) {
throw new MessagingException("getMessages", ioe); throw new MessagingException("getMessages", ioe);
} }
ArrayList<Message> messages = new ArrayList<Message>(); List<Message> messages = new ArrayList<Message>();
int i = 0; int i = 0;
for (int msgNum = start; msgNum <= end; msgNum++) { for (int msgNum = start; msgNum <= end; msgNum++) {
Pop3Message message = mMsgNumToMsgMap.get(msgNum); Pop3Message message = mMsgNumToMsgMap.get(msgNum);
@ -611,7 +601,7 @@ public class Pop3Store extends Store {
listener.messageFinished(message, i++, (end - start) + 1); listener.messageFinished(message, i++, (end - start) + 1);
} }
} }
return messages.toArray(new Message[messages.size()]); return messages;
} }
/** /**
@ -698,7 +688,7 @@ public class Pop3Store extends Store {
} }
} }
private void indexUids(ArrayList<String> uids) private void indexUids(List<String> uids)
throws MessagingException, IOException { throws MessagingException, IOException {
Set<String> unindexedUids = new HashSet<String>(); Set<String> unindexedUids = new HashSet<String>();
for (String uid : uids) { for (String uid : uids) {
@ -753,12 +743,12 @@ public class Pop3Store extends Store {
} }
@Override @Override
public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException { public List<? extends Message> getMessages(MessageRetrievalListener listener) throws MessagingException {
throw new UnsupportedOperationException("Pop3: No getMessages"); throw new UnsupportedOperationException("Pop3: No getMessages");
} }
@Override @Override
public Message[] getMessages(String[] uids, MessageRetrievalListener listener) public List<? extends Message> getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
throw new UnsupportedOperationException("Pop3: No getMessages by uids"); throw new UnsupportedOperationException("Pop3: No getMessages by uids");
} }
@ -771,12 +761,12 @@ public class Pop3Store extends Store {
* @throws MessagingException * @throws MessagingException
*/ */
@Override @Override
public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener) public void fetch(List<? extends Message> messages, FetchProfile fp, MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
if (messages == null || messages.length == 0) { if (messages == null || messages.isEmpty()) {
return; return;
} }
ArrayList<String> uids = new ArrayList<String>(); List<String> uids = new ArrayList<String>();
for (Message message : messages) { for (Message message : messages) {
uids.add(message.getUid()); uids.add(message.getUid());
} }
@ -798,8 +788,8 @@ public class Pop3Store extends Store {
} catch (IOException ioe) { } catch (IOException ioe) {
throw new MessagingException("fetch", ioe); throw new MessagingException("fetch", ioe);
} }
for (int i = 0, count = messages.length; i < count; i++) { for (int i = 0, count = messages.size(); i < count; i++) {
Message message = messages[i]; Message message = messages.get(i);
if (!(message instanceof Pop3Message)) { if (!(message instanceof Pop3Message)) {
throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message"); throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message");
} }
@ -837,7 +827,7 @@ public class Pop3Store extends Store {
} }
} }
private void fetchEnvelope(Message[] messages, private void fetchEnvelope(List<? extends Message> messages,
MessageRetrievalListener listener) throws IOException, MessagingException { MessageRetrievalListener listener) throws IOException, MessagingException {
int unsizedMessages = 0; int unsizedMessages = 0;
for (Message message : messages) { for (Message message : messages) {
@ -853,8 +843,8 @@ public class Pop3Store extends Store {
* In extreme cases we'll do a command per message instead of a bulk request * In extreme cases we'll do a command per message instead of a bulk request
* to hopefully save some time and bandwidth. * to hopefully save some time and bandwidth.
*/ */
for (int i = 0, count = messages.length; i < count; i++) { for (int i = 0, count = messages.size(); i < count; i++) {
Message message = messages[i]; Message message = messages.get(i);
if (!(message instanceof Pop3Message)) { if (!(message instanceof Pop3Message)) {
throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message"); throw new MessagingException("Pop3Store.fetch called with non-Pop3 Message");
} }
@ -877,7 +867,7 @@ public class Pop3Store extends Store {
for (Message message : messages) { for (Message message : messages) {
msgUidIndex.add(message.getUid()); msgUidIndex.add(message.getUid());
} }
int i = 0, count = messages.length; int i = 0, count = messages.size();
String response = executeSimpleCommand(LIST_COMMAND); String response = executeSimpleCommand(LIST_COMMAND);
while ((response = readLine()) != null) { while ((response = readLine()) != null) {
if (response.equals(".")) { if (response.equals(".")) {
@ -968,7 +958,7 @@ public class Pop3Store extends Store {
} }
@Override @Override
public Map<String, String> appendMessages(Message[] messages) throws MessagingException { public Map<String, String> appendMessages(List<? extends Message> messages) throws MessagingException {
return null; return null;
} }
@ -977,8 +967,8 @@ public class Pop3Store extends Store {
} }
@Override @Override
public void delete(Message[] msgs, String trashFolderName) throws MessagingException { public void delete(List<? extends Message> msgs, String trashFolderName) throws MessagingException {
setFlags(msgs, new Flag[] { Flag.DELETED }, true); setFlags(msgs, Collections.singleton(Flag.DELETED), true);
} }
@Override @Override
@ -987,20 +977,20 @@ public class Pop3Store extends Store {
} }
@Override @Override
public void setFlags(Flag[] flags, boolean value) throws MessagingException { public void setFlags(final Set<Flag> flags, boolean value) throws MessagingException {
throw new UnsupportedOperationException("POP3: No setFlags(Flag[],boolean)"); throw new UnsupportedOperationException("POP3: No setFlags(Set<Flag>,boolean)");
} }
@Override @Override
public void setFlags(Message[] messages, Flag[] flags, boolean value) public void setFlags(List<? extends Message> messages, final Set<Flag> flags, boolean value)
throws MessagingException { throws MessagingException {
if (!value || !Utility.arrayContains(flags, Flag.DELETED)) { if (!value || !flags.contains(Flag.DELETED)) {
/* /*
* The only flagging we support is setting the Deleted flag. * The only flagging we support is setting the Deleted flag.
*/ */
return; return;
} }
ArrayList<String> uids = new ArrayList<String>(); List<String> uids = new ArrayList<String>();
try { try {
for (Message message : messages) { for (Message message : messages) {
uids.add(message.getUid()); uids.add(message.getUid());
@ -1194,15 +1184,10 @@ public class Pop3Store extends Store {
mSize = size; mSize = size;
} }
@Override
protected void parse(InputStream in) throws IOException, MessagingException {
super.parse(in);
}
@Override @Override
public void setFlag(Flag flag, boolean set) throws MessagingException { public void setFlag(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set); super.setFlag(flag, set);
mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set); mFolder.setFlags(Collections.singletonList(this), Collections.singleton(flag), set);
} }
@Override @Override
@ -1240,9 +1225,9 @@ public class Pop3Store extends Store {
} }
static class Pop3ResponseInputStream extends InputStream { static class Pop3ResponseInputStream extends InputStream {
InputStream mIn; private InputStream mIn;
boolean mStartOfLine = true; private boolean mStartOfLine = true;
boolean mFinished; private boolean mFinished;
public Pop3ResponseInputStream(InputStream in) { public Pop3ResponseInputStream(InputStream in) {
mIn = in; mIn = in;

View File

@ -176,12 +176,12 @@ public class StorageManager {
/** /**
* The root of the denoted storage. Used for mount points checking. * The root of the denoted storage. Used for mount points checking.
*/ */
protected File mRoot; private File mRoot;
/** /**
* Choosen base directory * Choosen base directory
*/ */
protected File mApplicationDir; private File mApplicationDir;
@Override @Override
public void init(final Context context) { public void init(final Context context) {
@ -258,7 +258,7 @@ public class StorageManager {
public static final String ID = "InternalStorage"; public static final String ID = "InternalStorage";
protected File mRoot; private File mRoot;
@Override @Override
public String getId() { public String getId() {
@ -328,13 +328,14 @@ public class StorageManager {
/** /**
* Root of the denoted storage. * Root of the denoted storage.
*/ */
protected File mRoot; private File mRoot;
/** /**
* Choosen base directory. * Choosen base directory.
*/ */
protected File mApplicationDirectory; private File mApplicationDirectory;
@Override
public String getId() { public String getId() {
return ID; return ID;
} }
@ -392,6 +393,7 @@ public class StorageManager {
public static final String ID = "HtcIncredibleStorage"; public static final String ID = "HtcIncredibleStorage";
@Override
public String getId() { public String getId() {
return ID; return ID;
} }
@ -428,6 +430,7 @@ public class StorageManager {
public static final String ID = "SamsungGalaxySStorage"; public static final String ID = "SamsungGalaxySStorage";
@Override
public String getId() { public String getId() {
return ID; return ID;
} }

View File

@ -5,6 +5,8 @@ import android.util.Log;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.controller.MessageRetrievalListener; import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*; import com.fsck.k9.mail.*;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream; import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
@ -40,8 +42,6 @@ import javax.xml.parsers.SAXParserFactory;
import java.io.*; import java.io.*;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.text.DateFormat; import java.text.DateFormat;
@ -65,8 +65,6 @@ public class WebDavStore extends Store {
private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
// These are the ids used from Exchange server to identify the special folders // These are the ids used from Exchange server to identify the special folders
// http://social.technet.microsoft.com/Forums/en/exchangesvrdevelopment/thread/1cd2e98c-8a12-44bd-a3e3-9c5ee9e4e14d // http://social.technet.microsoft.com/Forums/en/exchangesvrdevelopment/thread/1cd2e98c-8a12-44bd-a3e3-9c5ee9e4e14d
private static final String DAV_MAIL_INBOX_FOLDER = "inbox"; private static final String DAV_MAIL_INBOX_FOLDER = "inbox";
@ -139,9 +137,8 @@ public class WebDavStore extends Store {
String userInfo = webDavUri.getUserInfo(); String userInfo = webDavUri.getUserInfo();
if (userInfo != null) { if (userInfo != null) {
try {
String[] userInfoParts = userInfo.split(":"); String[] userInfoParts = userInfo.split(":");
username = URLDecoder.decode(userInfoParts[0], "UTF-8"); username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
String userParts[] = username.split("\\\\", 2); String userParts[] = username.split("\\\\", 2);
if (userParts.length > 1) { if (userParts.length > 1) {
@ -150,11 +147,7 @@ public class WebDavStore extends Store {
alias = username; alias = username;
} }
if (userInfoParts.length > 1) { if (userInfoParts.length > 1) {
password = URLDecoder.decode(userInfoParts[1], "UTF-8"); password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
}
} catch (UnsupportedEncodingException enc) {
// This shouldn't happen since the encoding is hardcoded to UTF-8
throw new IllegalArgumentException("Couldn't urldecode username or password.", enc);
} }
} }
@ -194,16 +187,9 @@ public class WebDavStore extends Store {
* @see WebDavStore#decodeUri(String) * @see WebDavStore#decodeUri(String)
*/ */
public static String createUri(ServerSettings server) { public static String createUri(ServerSettings server) {
String userEnc; String userEnc = UrlEncodingHelper.encodeUtf8(server.username);
String passwordEnc; String passwordEnc = (server.password != null) ?
try { UrlEncodingHelper.encodeUtf8(server.password) : "";
userEnc = URLEncoder.encode(server.username, "UTF-8");
passwordEnc = (server.password != null) ?
URLEncoder.encode(server.password, "UTF-8") : "";
}
catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Could not encode username or password", e);
}
String scheme; String scheme;
switch (server.connectionSecurity) { switch (server.connectionSecurity) {
@ -305,7 +291,7 @@ public class WebDavStore extends Store {
private String mCachedLoginUrl; private String mCachedLoginUrl;
private Folder mSendFolder = null; private Folder mSendFolder = null;
private HashMap<String, WebDavFolder> mFolderList = new HashMap<String, WebDavFolder>(); private Map<String, WebDavFolder> mFolderList = new HashMap<String, WebDavFolder>();
public WebDavStore(Account account) throws MessagingException { public WebDavStore(Account account) throws MessagingException {
@ -375,7 +361,7 @@ public class WebDavStore extends Store {
@Override @Override
public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException { public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException {
LinkedList<Folder> folderList = new LinkedList<Folder>(); List<Folder> folderList = new LinkedList<Folder>();
/** /**
* We have to check authentication here so we have the proper URL stored * We have to check authentication here so we have the proper URL stored
*/ */
@ -385,13 +371,13 @@ public class WebDavStore extends Store {
* Firstly we get the "special" folders list (inbox, outbox, etc) * Firstly we get the "special" folders list (inbox, outbox, etc)
* and setup the account accordingly * and setup the account accordingly
*/ */
HashMap<String, String> headers = new HashMap<String, String>(); Map<String, String> headers = new HashMap<String, String>();
DataSet dataset = new DataSet(); DataSet dataset = new DataSet();
headers.put("Depth", "0"); headers.put("Depth", "0");
headers.put("Brief", "t"); headers.put("Brief", "t");
dataset = processRequest(this.mUrl, "PROPFIND", getSpecialFoldersList(), headers); dataset = processRequest(this.mUrl, "PROPFIND", getSpecialFoldersList(), headers);
HashMap<String, String> specialFoldersMap = dataset.getSpecialFolderToUrl(); Map<String, String> specialFoldersMap = dataset.getSpecialFolderToUrl();
String folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_INBOX_FOLDER)); String folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_INBOX_FOLDER));
if (folderName != null) { if (folderName != null) {
mAccount.setAutoExpandFolderName(folderName); mAccount.setAutoExpandFolderName(folderName);
@ -479,7 +465,6 @@ public class WebDavStore extends Store {
} }
if (folderSlash > 0) { if (folderSlash > 0) {
String folderName;
String fullPathName; String fullPathName;
// Removes the final slash if present // Removes the final slash if present
@ -489,17 +474,8 @@ public class WebDavStore extends Store {
fullPathName = folderUrl.substring(folderSlash + 1); fullPathName = folderUrl.substring(folderSlash + 1);
// Decodes the url-encoded folder name (i.e. "My%20folder" => "My Folder" // Decodes the url-encoded folder name (i.e. "My%20folder" => "My Folder"
try {
folderName = java.net.URLDecoder.decode(fullPathName, "UTF-8");
} catch (UnsupportedEncodingException uee) {
/**
* If we don't support UTF-8 there's a problem, don't decode
* it then
*/
folderName = fullPathName;
}
return folderName; return UrlEncodingHelper.decodeUtf8(fullPathName);
} }
return null; return null;
@ -847,7 +823,7 @@ public class WebDavStore extends Store {
request.setMethod("POST"); request.setMethod("POST");
// Build the POST data. // Build the POST data.
ArrayList<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>(); List<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>();
pairs.add(new BasicNameValuePair("destination", mUrl)); pairs.add(new BasicNameValuePair("destination", mUrl));
pairs.add(new BasicNameValuePair("username", mUsername)); pairs.add(new BasicNameValuePair("username", mUsername));
pairs.add(new BasicNameValuePair("password", mPassword)); pairs.add(new BasicNameValuePair("password", mPassword));
@ -1059,7 +1035,7 @@ public class WebDavStore extends Store {
} }
private InputStream sendRequest(String url, String method, StringEntity messageBody, private InputStream sendRequest(String url, String method, StringEntity messageBody,
HashMap<String, String> headers, boolean tryAuth) Map<String, String> headers, boolean tryAuth)
throws MessagingException { throws MessagingException {
InputStream istream = null; InputStream istream = null;
@ -1137,12 +1113,12 @@ public class WebDavStore extends Store {
* not all requests will need them. There are two signatures to support calls that don't require parsing of the * not all requests will need them. There are two signatures to support calls that don't require parsing of the
* response. * response.
*/ */
private DataSet processRequest(String url, String method, String messageBody, HashMap<String, String> headers) private DataSet processRequest(String url, String method, String messageBody, Map<String, String> headers)
throws MessagingException { throws MessagingException {
return processRequest(url, method, messageBody, headers, true); return processRequest(url, method, messageBody, headers, true);
} }
private DataSet processRequest(String url, String method, String messageBody, HashMap<String, String> headers, private DataSet processRequest(String url, String method, String messageBody, Map<String, String> headers,
boolean needsParsing) boolean needsParsing)
throws MessagingException { throws MessagingException {
DataSet dataset = new DataSet(); DataSet dataset = new DataSet();
@ -1219,11 +1195,11 @@ public class WebDavStore extends Store {
} }
@Override @Override
public void sendMessages(Message[] messages) throws MessagingException { public void sendMessages(List<? extends Message> messages) throws MessagingException {
WebDavFolder tmpFolder = (WebDavStore.WebDavFolder) getFolder(mAccount.getDraftsFolderName()); WebDavFolder tmpFolder = (WebDavStore.WebDavFolder) getFolder(mAccount.getDraftsFolderName());
try { try {
tmpFolder.open(Folder.OPEN_MODE_RW); tmpFolder.open(Folder.OPEN_MODE_RW);
Message[] retMessages = tmpFolder.appendWebDavMessages(messages); List<? extends Message> retMessages = tmpFolder.appendWebDavMessages(messages);
tmpFolder.moveMessages(retMessages, getSendSpoolFolder()); tmpFolder.moveMessages(retMessages, getSendSpoolFolder());
} finally { } finally {
@ -1258,21 +1234,16 @@ public class WebDavStore extends Store {
this.mName = name; this.mName = name;
String encodedName = ""; String encodedName = "";
try {
String[] urlParts = name.split("/"); String[] urlParts = name.split("/");
String url = ""; String url = "";
for (int i = 0, count = urlParts.length; i < count; i++) { for (int i = 0, count = urlParts.length; i < count; i++) {
if (i != 0) { if (i != 0) {
url = url + "/" + java.net.URLEncoder.encode(urlParts[i], "UTF-8"); url = url + "/" + UrlEncodingHelper.encodeUtf8(urlParts[i]);
} else { } else {
url = java.net.URLEncoder.encode(urlParts[i], "UTF-8"); url = UrlEncodingHelper.encodeUtf8(urlParts[i]);
} }
} }
encodedName = url; encodedName = url;
} catch (UnsupportedEncodingException uee) {
Log.e(K9.LOG_TAG, "UnsupportedEncodingException URLEncoding folder name, skipping encoded");
encodedName = name;
}
encodedName = encodedName.replaceAll("\\+", "%20"); encodedName = encodedName.replaceAll("\\+", "%20");
@ -1297,38 +1268,38 @@ public class WebDavStore extends Store {
} }
@Override @Override
public Map<String, String> copyMessages(Message[] messages, Folder folder) throws MessagingException { public Map<String, String> copyMessages(List<? extends Message> messages, Folder folder) throws MessagingException {
moveOrCopyMessages(messages, folder.getName(), false); moveOrCopyMessages(messages, folder.getName(), false);
return null; return null;
} }
@Override @Override
public Map<String, String> moveMessages(Message[] messages, Folder folder) throws MessagingException { public Map<String, String> moveMessages(List<? extends Message> messages, Folder folder) throws MessagingException {
moveOrCopyMessages(messages, folder.getName(), true); moveOrCopyMessages(messages, folder.getName(), true);
return null; return null;
} }
@Override @Override
public void delete(Message[] msgs, String trashFolderName) throws MessagingException { public void delete(List<? extends Message> msgs, String trashFolderName) throws MessagingException {
moveOrCopyMessages(msgs, trashFolderName, true); moveOrCopyMessages(msgs, trashFolderName, true);
} }
private void moveOrCopyMessages(Message[] messages, String folderName, boolean isMove) private void moveOrCopyMessages(List<? extends Message> messages, String folderName, boolean isMove)
throws MessagingException { throws MessagingException {
String[] uids = new String[messages.length]; String[] uids = new String[messages.size()];
for (int i = 0, count = messages.length; i < count; i++) { for (int i = 0, count = messages.size(); i < count; i++) {
uids[i] = messages[i].getUid(); uids[i] = messages.get(i).getUid();
} }
String messageBody = ""; String messageBody = "";
HashMap<String, String> headers = new HashMap<String, String>(); Map<String, String> headers = new HashMap<String, String>();
HashMap<String, String> uidToUrl = getMessageUrls(uids); Map<String, String> uidToUrl = getMessageUrls(uids);
String[] urls = new String[uids.length]; String[] urls = new String[uids.length];
for (int i = 0, count = uids.length; i < count; i++) { for (int i = 0, count = uids.length; i < count; i++) {
urls[i] = uidToUrl.get(uids[i]); urls[i] = uidToUrl.get(uids[i]);
if (urls[i] == null && messages[i] instanceof WebDavMessage) { if (urls[i] == null && messages.get(i) instanceof WebDavMessage) {
WebDavMessage wdMessage = (WebDavMessage) messages[i]; WebDavMessage wdMessage = (WebDavMessage) messages.get(i);
urls[i] = wdMessage.getUrl(); urls[i] = wdMessage.getUrl();
} }
} }
@ -1339,7 +1310,7 @@ public class WebDavStore extends Store {
headers.put("Brief", "t"); headers.put("Brief", "t");
headers.put("If-Match", "*"); headers.put("If-Match", "*");
String action = (isMove ? "BMOVE" : "BCOPY"); String action = (isMove ? "BMOVE" : "BCOPY");
Log.i(K9.LOG_TAG, "Moving " + messages.length + " messages to " + destFolder.mFolderUrl); Log.i(K9.LOG_TAG, "Moving " + messages.size() + " messages to " + destFolder.mFolderUrl);
processRequest(mFolderUrl, action, messageBody, headers, false); processRequest(mFolderUrl, action, messageBody, headers, false);
} }
@ -1347,7 +1318,7 @@ public class WebDavStore extends Store {
private int getMessageCount(boolean read) throws MessagingException { private int getMessageCount(boolean read) throws MessagingException {
String isRead; String isRead;
int messageCount = 0; int messageCount = 0;
HashMap<String, String> headers = new HashMap<String, String>(); Map<String, String> headers = new HashMap<String, String>();
String messageBody; String messageBody;
if (read) { if (read) {
@ -1431,11 +1402,11 @@ public class WebDavStore extends Store {
} }
@Override @Override
public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) public List<? extends Message> getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
ArrayList<Message> messages = new ArrayList<Message>(); List<Message> messages = new ArrayList<Message>();
String[] uids; String[] uids;
HashMap<String, String> headers = new HashMap<String, String>(); Map<String, String> headers = new HashMap<String, String>();
int uidsLength = -1; int uidsLength = -1;
String messageBody; String messageBody;
@ -1461,7 +1432,7 @@ public class WebDavStore extends Store {
DataSet dataset = processRequest(this.mFolderUrl, "SEARCH", messageBody, headers); DataSet dataset = processRequest(this.mFolderUrl, "SEARCH", messageBody, headers);
uids = dataset.getUids(); uids = dataset.getUids();
HashMap<String, String> uidToUrl = dataset.getUidToUrl(); Map<String, String> uidToUrl = dataset.getUidToUrl();
uidsLength = uids.length; uidsLength = uids.length;
for (int i = 0; i < uidsLength; i++) { for (int i = 0; i < uidsLength; i++) {
@ -1477,22 +1448,22 @@ public class WebDavStore extends Store {
} }
} }
return messages.toArray(EMPTY_MESSAGE_ARRAY); return messages;
} }
@Override @Override
public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException { public List<? extends Message> getMessages(MessageRetrievalListener listener) throws MessagingException {
return getMessages(null, listener); return getMessages(null, listener);
} }
@Override @Override
public Message[] getMessages(String[] uids, MessageRetrievalListener listener) throws MessagingException { public List<? extends Message> getMessages(String[] uids, MessageRetrievalListener listener) throws MessagingException {
ArrayList<Message> messageList = new ArrayList<Message>(); List<Message> messageList = new ArrayList<Message>();
Message[] messages; List<? extends Message> messages;
if (uids == null || if (uids == null ||
uids.length == 0) { uids.length == 0) {
return messageList.toArray(EMPTY_MESSAGE_ARRAY); return messageList;
} }
for (int i = 0, count = uids.length; i < count; i++) { for (int i = 0, count = uids.length; i < count; i++) {
@ -1507,13 +1478,13 @@ public class WebDavStore extends Store {
listener.messageFinished(message, i, count); listener.messageFinished(message, i, count);
} }
} }
messages = messageList.toArray(EMPTY_MESSAGE_ARRAY); messages = messageList;
return messages; return messages;
} }
private HashMap<String, String> getMessageUrls(String[] uids) throws MessagingException { private Map<String, String> getMessageUrls(String[] uids) throws MessagingException {
HashMap<String, String> headers = new HashMap<String, String>(); Map<String, String> headers = new HashMap<String, String>();
String messageBody; String messageBody;
/** Retrieve and parse the XML entity for our messages */ /** Retrieve and parse the XML entity for our messages */
@ -1521,16 +1492,16 @@ public class WebDavStore extends Store {
headers.put("Brief", "t"); headers.put("Brief", "t");
DataSet dataset = processRequest(this.mFolderUrl, "SEARCH", messageBody, headers); DataSet dataset = processRequest(this.mFolderUrl, "SEARCH", messageBody, headers);
HashMap<String, String> uidToUrl = dataset.getUidToUrl(); Map<String, String> uidToUrl = dataset.getUidToUrl();
return uidToUrl; return uidToUrl;
} }
@Override @Override
public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener) public void fetch(List<? extends Message> messages, FetchProfile fp, MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
if (messages == null || if (messages == null ||
messages.length == 0) { messages.isEmpty()) {
return; return;
} }
@ -1562,7 +1533,7 @@ public class WebDavStore extends Store {
/** /**
* Fetches the full messages or up to lines lines and passes them to the message parser. * Fetches the full messages or up to lines lines and passes them to the message parser.
*/ */
private void fetchMessages(Message[] messages, MessageRetrievalListener listener, int lines) private void fetchMessages(List<? extends Message> messages, MessageRetrievalListener listener, int lines)
throws MessagingException { throws MessagingException {
WebDavHttpClient httpclient; WebDavHttpClient httpclient;
httpclient = getHttpClient(); httpclient = getHttpClient();
@ -1570,15 +1541,15 @@ public class WebDavStore extends Store {
/** /**
* We can't hand off to processRequest() since we need the stream to parse. * We can't hand off to processRequest() since we need the stream to parse.
*/ */
for (int i = 0, count = messages.length; i < count; i++) { for (int i = 0, count = messages.size(); i < count; i++) {
WebDavMessage wdMessage; WebDavMessage wdMessage;
int statusCode = 0; int statusCode = 0;
if (!(messages[i] instanceof WebDavMessage)) { if (!(messages.get(i) instanceof WebDavMessage)) {
throw new MessagingException("WebDavStore fetch called with non-WebDavMessage"); throw new MessagingException("WebDavStore fetch called with non-WebDavMessage");
} }
wdMessage = (WebDavMessage) messages[i]; wdMessage = (WebDavMessage) messages.get(i);
if (listener != null) { if (listener != null) {
listener.messageStarted(wdMessage.getUid(), i, count); listener.messageStarted(wdMessage.getUid(), i, count);
@ -1676,36 +1647,36 @@ public class WebDavStore extends Store {
* Fetches and sets the message flags for the supplied messages. The idea is to have this be recursive so that * Fetches and sets the message flags for the supplied messages. The idea is to have this be recursive so that
* we do a series of medium calls instead of one large massive call or a large number of smaller calls. * we do a series of medium calls instead of one large massive call or a large number of smaller calls.
*/ */
private void fetchFlags(Message[] startMessages, MessageRetrievalListener listener) throws MessagingException { private void fetchFlags(List<? extends Message> startMessages, MessageRetrievalListener listener) throws MessagingException {
HashMap<String, String> headers = new HashMap<String, String>(); HashMap<String, String> headers = new HashMap<String, String>();
String messageBody = ""; String messageBody = "";
Message[] messages = new Message[20]; List<Message> messages = new ArrayList<Message>(20);
String[] uids; String[] uids;
if (startMessages == null || if (startMessages == null ||
startMessages.length == 0) { startMessages.isEmpty()) {
return; return;
} }
if (startMessages.length > 20) { if (startMessages.size() > 20) {
Message[] newMessages = new Message[startMessages.length - 20]; List<Message> newMessages = new ArrayList<Message>(startMessages.size() - 20);
for (int i = 0, count = startMessages.length; i < count; i++) { for (int i = 0, count = startMessages.size(); i < count; i++) {
if (i < 20) { if (i < 20) {
messages[i] = startMessages[i]; messages.set(i, startMessages.get(i));
} else { } else {
newMessages[i - 20] = startMessages[i]; newMessages.set(i - 20, startMessages.get(i));
} }
} }
fetchFlags(newMessages, listener); fetchFlags(newMessages, listener);
} else { } else {
messages = startMessages; messages.addAll(startMessages);
} }
uids = new String[messages.length]; uids = new String[messages.size()];
for (int i = 0, count = messages.length; i < count; i++) { for (int i = 0, count = messages.size(); i < count; i++) {
uids[i] = messages[i].getUid(); uids[i] = messages.get(i).getUid();
} }
messageBody = getMessageFlagsXml(uids); messageBody = getMessageFlagsXml(uids);
@ -1716,13 +1687,13 @@ public class WebDavStore extends Store {
throw new MessagingException("Data Set from request was null"); throw new MessagingException("Data Set from request was null");
} }
HashMap<String, Boolean> uidToReadStatus = dataset.getUidToRead(); Map<String, Boolean> uidToReadStatus = dataset.getUidToRead();
for (int i = 0, count = messages.length; i < count; i++) { for (int i = 0, count = messages.size(); i < count; i++) {
if (!(messages[i] instanceof WebDavMessage)) { if (!(messages.get(i) instanceof WebDavMessage)) {
throw new MessagingException("WebDavStore fetch called with non-WebDavMessage"); throw new MessagingException("WebDavStore fetch called with non-WebDavMessage");
} }
WebDavMessage wdMessage = (WebDavMessage) messages[i]; WebDavMessage wdMessage = (WebDavMessage) messages.get(i);
if (listener != null) { if (listener != null) {
listener.messageStarted(wdMessage.getUid(), i, count); listener.messageStarted(wdMessage.getUid(), i, count);
@ -1745,37 +1716,37 @@ public class WebDavStore extends Store {
* that we do a series of medium calls instead of one large massive call or a large number of smaller calls. * that we do a series of medium calls instead of one large massive call or a large number of smaller calls.
* Call it a happy balance * Call it a happy balance
*/ */
private void fetchEnvelope(Message[] startMessages, MessageRetrievalListener listener) private void fetchEnvelope(List<? extends Message> startMessages, MessageRetrievalListener listener)
throws MessagingException { throws MessagingException {
HashMap<String, String> headers = new HashMap<String, String>(); Map<String, String> headers = new HashMap<String, String>();
String messageBody = ""; String messageBody = "";
String[] uids; String[] uids;
Message[] messages = new Message[10]; List<Message> messages = new ArrayList<Message>(10);
if (startMessages == null || if (startMessages == null ||
startMessages.length == 0) { startMessages.isEmpty()) {
return; return;
} }
if (startMessages.length > 10) { if (startMessages.size() > 10) {
Message[] newMessages = new Message[startMessages.length - 10]; List<Message> newMessages = new ArrayList<Message>(startMessages.size() - 10);
for (int i = 0, count = startMessages.length; i < count; i++) { for (int i = 0, count = startMessages.size(); i < count; i++) {
if (i < 10) { if (i < 10) {
messages[i] = startMessages[i]; messages.set(i, startMessages.get(i));
} else { } else {
newMessages[i - 10] = startMessages[i]; newMessages.set(i - 10,startMessages.get(i));
} }
} }
fetchEnvelope(newMessages, listener); fetchEnvelope(newMessages, listener);
} else { } else {
messages = startMessages; messages.addAll(startMessages);
} }
uids = new String[messages.length]; uids = new String[messages.size()];
for (int i = 0, count = messages.length; i < count; i++) { for (int i = 0, count = messages.size(); i < count; i++) {
uids[i] = messages[i].getUid(); uids[i] = messages.get(i).getUid();
} }
messageBody = getMessageEnvelopeXml(uids); messageBody = getMessageEnvelopeXml(uids);
@ -1784,15 +1755,15 @@ public class WebDavStore extends Store {
Map<String, ParsedMessageEnvelope> envelopes = dataset.getMessageEnvelopes(); Map<String, ParsedMessageEnvelope> envelopes = dataset.getMessageEnvelopes();
int count = messages.length; int count = messages.size();
for (int i = messages.length - 1; i >= 0; i--) { for (int i = messages.size() - 1; i >= 0; i--) {
if (!(messages[i] instanceof WebDavMessage)) { if (!(messages.get(i) instanceof WebDavMessage)) {
throw new MessagingException("WebDavStore fetch called with non-WebDavMessage"); throw new MessagingException("WebDavStore fetch called with non-WebDavMessage");
} }
WebDavMessage wdMessage = (WebDavMessage) messages[i]; WebDavMessage wdMessage = (WebDavMessage) messages.get(i);
if (listener != null) { if (listener != null) {
listener.messageStarted(messages[i].getUid(), i, count); listener.messageStarted(messages.get(i).getUid(), i, count);
} }
ParsedMessageEnvelope envelope = envelopes.get(wdMessage.getUid()); ParsedMessageEnvelope envelope = envelopes.get(wdMessage.getUid());
@ -1804,18 +1775,18 @@ public class WebDavStore extends Store {
} }
if (listener != null) { if (listener != null) {
listener.messageFinished(messages[i], i, count); listener.messageFinished(messages.get(i), i, count);
} }
} }
} }
@Override @Override
public void setFlags(Message[] messages, Flag[] flags, boolean value) public void setFlags(List<? extends Message> messages, final Set<Flag> flags, boolean value)
throws MessagingException { throws MessagingException {
String[] uids = new String[messages.length]; String[] uids = new String[messages.size()];
for (int i = 0, count = messages.length; i < count; i++) { for (int i = 0, count = messages.size(); i < count; i++) {
uids[i] = messages[i].getUid(); uids[i] = messages.get(i).getUid();
} }
for (Flag flag : flags) { for (Flag flag : flags) {
@ -1829,8 +1800,8 @@ public class WebDavStore extends Store {
private void markServerMessagesRead(String[] uids, boolean read) throws MessagingException { private void markServerMessagesRead(String[] uids, boolean read) throws MessagingException {
String messageBody = ""; String messageBody = "";
HashMap<String, String> headers = new HashMap<String, String>(); Map<String, String> headers = new HashMap<String, String>();
HashMap<String, String> uidToUrl = getMessageUrls(uids); Map<String, String> uidToUrl = getMessageUrls(uids);
String[] urls = new String[uids.length]; String[] urls = new String[uids.length];
for (int i = 0, count = uids.length; i < count; i++) { for (int i = 0, count = uids.length; i < count; i++) {
@ -1845,10 +1816,10 @@ public class WebDavStore extends Store {
} }
private void deleteServerMessages(String[] uids) throws MessagingException { private void deleteServerMessages(String[] uids) throws MessagingException {
HashMap<String, String> uidToUrl = getMessageUrls(uids); Map<String, String> uidToUrl = getMessageUrls(uids);
for (String uid : uids) { for (String uid : uids) {
HashMap<String, String> headers = new HashMap<String, String>(); Map<String, String> headers = new HashMap<String, String>();
String url = uidToUrl.get(uid); String url = uidToUrl.get(uid);
String destinationUrl = generateDeleteUrl(url); String destinationUrl = generateDeleteUrl(url);
@ -1875,13 +1846,13 @@ public class WebDavStore extends Store {
} }
@Override @Override
public Map<String, String> appendMessages(Message[] messages) throws MessagingException { public Map<String, String> appendMessages(List<? extends Message> messages) throws MessagingException {
appendWebDavMessages(messages); appendWebDavMessages(messages);
return null; return null;
} }
public Message[] appendWebDavMessages(Message[] messages) throws MessagingException { public List<? extends Message> appendWebDavMessages(List<? extends Message> messages) throws MessagingException {
Message[] retMessages = new Message[messages.length]; List<Message> retMessages = new ArrayList<Message>(messages.size());
int ind = 0; int ind = 0;
WebDavHttpClient httpclient = getHttpClient(); WebDavHttpClient httpclient = getHttpClient();
@ -1910,7 +1881,7 @@ public class WebDavStore extends Store {
if (!messageURL.endsWith("/")) { if (!messageURL.endsWith("/")) {
messageURL += "/"; messageURL += "/";
} }
messageURL += URLEncoder.encode(message.getUid() + ":" + System.currentTimeMillis() + ".eml", "UTF-8"); messageURL += UrlEncodingHelper.encodeUtf8(message.getUid() + ":" + System.currentTimeMillis() + ".eml");
Log.i(K9.LOG_TAG, "Uploading message as " + messageURL); Log.i(K9.LOG_TAG, "Uploading message as " + messageURL);
@ -1936,7 +1907,7 @@ public class WebDavStore extends Store {
WebDavMessage retMessage = new WebDavMessage(message.getUid(), this); WebDavMessage retMessage = new WebDavMessage(message.getUid(), this);
retMessage.setUrl(messageURL); retMessage.setUrl(messageURL);
retMessages[ind++] = retMessage; retMessages.set(ind++, retMessage);
} catch (Exception e) { } catch (Exception e) {
throw new MessagingException("Unable to append", e); throw new MessagingException("Unable to append", e);
} }
@ -1958,9 +1929,9 @@ public class WebDavStore extends Store {
} }
@Override @Override
public void setFlags(Flag[] flags, boolean value) throws MessagingException { public void setFlags(final Set<Flag> flags, boolean value) throws MessagingException {
Log.e(K9.LOG_TAG, Log.e(K9.LOG_TAG,
"Unimplemented method setFlags(Flag[], boolean) breaks markAllMessagesAsRead and EmptyTrash"); "Unimplemented method setFlags(Set<Flag>, boolean) breaks markAllMessagesAsRead and EmptyTrash");
// Try to make this efficient by not retrieving all of the messages // Try to make this efficient by not retrieving all of the messages
} }
} }
@ -1997,12 +1968,9 @@ public class WebDavStore extends Store {
* We have to decode, then encode the URL because Exchange likes to not properly encode all characters * We have to decode, then encode the URL because Exchange likes to not properly encode all characters
*/ */
try { try {
end = java.net.URLDecoder.decode(end, "UTF-8"); end = UrlEncodingHelper.decodeUtf8(end);
end = java.net.URLEncoder.encode(end, "UTF-8"); end = UrlEncodingHelper.encodeUtf8(end);
end = end.replaceAll("\\+", "%20"); end = end.replaceAll("\\+", "%20");
} catch (UnsupportedEncodingException uee) {
Log.e(K9.LOG_TAG, "UnsupportedEncodingException caught in setUrl: " + uee + "\nTrace: "
+ processException(uee));
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
Log.e(K9.LOG_TAG, "IllegalArgumentException caught in setUrl: " + iae + "\nTrace: " Log.e(K9.LOG_TAG, "IllegalArgumentException caught in setUrl: " + iae + "\nTrace: "
+ processException(iae)); + processException(iae));
@ -2029,18 +1997,13 @@ public class WebDavStore extends Store {
this.mSize = size; this.mSize = size;
} }
@Override
public void parse(InputStream in) throws IOException, MessagingException {
super.parse(in);
}
public void setFlagInternal(Flag flag, boolean set) throws MessagingException { public void setFlagInternal(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set); super.setFlag(flag, set);
} }
public void setNewHeaders(ParsedMessageEnvelope envelope) throws MessagingException { public void setNewHeaders(ParsedMessageEnvelope envelope) throws MessagingException {
String[] headers = envelope.getHeaderList(); String[] headers = envelope.getHeaderList();
HashMap<String, String> messageHeaders = envelope.getMessageHeaders(); Map<String, String> messageHeaders = envelope.getMessageHeaders();
for (String header : headers) { for (String header : headers) {
String headerValue = messageHeaders.get(header); String headerValue = messageHeaders.get(header);
@ -2060,13 +2023,13 @@ public class WebDavStore extends Store {
public void delete(String trashFolderName) throws MessagingException { public void delete(String trashFolderName) throws MessagingException {
WebDavFolder wdFolder = (WebDavFolder) getFolder(); WebDavFolder wdFolder = (WebDavFolder) getFolder();
Log.i(K9.LOG_TAG, "Deleting message by moving to " + trashFolderName); Log.i(K9.LOG_TAG, "Deleting message by moving to " + trashFolderName);
wdFolder.moveMessages(new Message[] { this }, wdFolder.getStore().getFolder(trashFolderName)); wdFolder.moveMessages(Collections.singletonList(this), wdFolder.getStore().getFolder(trashFolderName));
} }
@Override @Override
public void setFlag(Flag flag, boolean set) throws MessagingException { public void setFlag(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set); super.setFlag(flag, set);
mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set); mFolder.setFlags(Collections.singletonList(this), Collections.singleton(flag), set);
} }
} }
@ -2142,8 +2105,8 @@ public class WebDavStore extends Store {
private boolean mReadStatus = false; private boolean mReadStatus = false;
private String mUid = ""; private String mUid = "";
private HashMap<String, String> mMessageHeaders = new HashMap<String, String>(); private Map<String, String> mMessageHeaders = new HashMap<String, String>();
private ArrayList<String> mHeaders = new ArrayList<String>(); private List<String> mHeaders = new ArrayList<String>();
public void addHeader(String field, String value) { public void addHeader(String field, String value) {
String headerName = HEADER_MAPPINGS.get(field); String headerName = HEADER_MAPPINGS.get(field);
@ -2154,7 +2117,7 @@ public class WebDavStore extends Store {
} }
} }
public HashMap<String, String> getMessageHeaders() { public Map<String, String> getMessageHeaders() {
return this.mMessageHeaders; return this.mMessageHeaders;
} }
@ -2186,9 +2149,9 @@ public class WebDavStore extends Store {
* depending on the accessor calls made. * depending on the accessor calls made.
*/ */
public class DataSet { public class DataSet {
private HashMap<String, HashMap<String, String>> mData = new HashMap<String, HashMap<String, String>>(); private Map<String, Map<String, String>> mData = new HashMap<String, Map<String, String>>();
private StringBuilder mUid = new StringBuilder(); private StringBuilder mUid = new StringBuilder();
private HashMap<String, String> mTempData = new HashMap<String, String>(); private Map<String, String> mTempData = new HashMap<String, String>();
public void addValue(String value, String tagName) { public void addValue(String value, String tagName) {
if (tagName.equals("uid")) { if (tagName.equals("uid")) {
@ -2220,9 +2183,9 @@ public class WebDavStore extends Store {
/** /**
* Returns a hashmap of special folder name => special folder url * Returns a hashmap of special folder name => special folder url
*/ */
public HashMap<String, String> getSpecialFolderToUrl() { public Map<String, String> getSpecialFolderToUrl() {
// We return the first (and only) map // We return the first (and only) map
for (HashMap<String, String> folderMap : mData.values()) { for (Map<String, String> folderMap : mData.values()) {
return folderMap; return folderMap;
} }
return new HashMap<String, String>(); return new HashMap<String, String>();
@ -2231,11 +2194,11 @@ public class WebDavStore extends Store {
/** /**
* Returns a hashmap of Message UID => Message Url * Returns a hashmap of Message UID => Message Url
*/ */
public HashMap<String, String> getUidToUrl() { public Map<String, String> getUidToUrl() {
HashMap<String, String> uidToUrl = new HashMap<String, String>(); Map<String, String> uidToUrl = new HashMap<String, String>();
for (String uid : mData.keySet()) { for (String uid : mData.keySet()) {
HashMap<String, String> data = mData.get(uid); Map<String, String> data = mData.get(uid);
String value = data.get("href"); String value = data.get("href");
if (value != null && if (value != null &&
!value.equals("")) { !value.equals("")) {
@ -2249,11 +2212,11 @@ public class WebDavStore extends Store {
/** /**
* Returns a hashmap of Message UID => Read Status * Returns a hashmap of Message UID => Read Status
*/ */
public HashMap<String, Boolean> getUidToRead() { public Map<String, Boolean> getUidToRead() {
HashMap<String, Boolean> uidToRead = new HashMap<String, Boolean>(); Map<String, Boolean> uidToRead = new HashMap<String, Boolean>();
for (String uid : mData.keySet()) { for (String uid : mData.keySet()) {
HashMap<String, String> data = mData.get(uid); Map<String, String> data = mData.get(uid);
String readStatus = data.get("read"); String readStatus = data.get("read");
if (readStatus != null && !readStatus.equals("")) { if (readStatus != null && !readStatus.equals("")) {
Boolean value = !readStatus.equals("0"); Boolean value = !readStatus.equals("0");
@ -2273,10 +2236,10 @@ public class WebDavStore extends Store {
* Returns an array of all hrefs (urls) that were received * Returns an array of all hrefs (urls) that were received
*/ */
public String[] getHrefs() { public String[] getHrefs() {
ArrayList<String> hrefs = new ArrayList<String>(); List<String> hrefs = new ArrayList<String>();
for (String uid : mData.keySet()) { for (String uid : mData.keySet()) {
HashMap<String, String> data = mData.get(uid); Map<String, String> data = mData.get(uid);
String href = data.get("href"); String href = data.get("href");
hrefs.add(href); hrefs.add(href);
} }
@ -2288,7 +2251,7 @@ public class WebDavStore extends Store {
* Return an array of all Message UIDs that were received * Return an array of all Message UIDs that were received
*/ */
public String[] getUids() { public String[] getUids() {
ArrayList<String> uids = new ArrayList<String>(); List<String> uids = new ArrayList<String>();
for (String uid : mData.keySet()) { for (String uid : mData.keySet()) {
uids.add(uid); uids.add(uid);
@ -2309,7 +2272,7 @@ public class WebDavStore extends Store {
int messageCount = 0; int messageCount = 0;
for (String uid : mData.keySet()) { for (String uid : mData.keySet()) {
HashMap<String, String> data = mData.get(uid); Map<String, String> data = mData.get(uid);
String count = data.get("visiblecount"); String count = data.get("visiblecount");
if (count != null && if (count != null &&
@ -2323,14 +2286,14 @@ public class WebDavStore extends Store {
} }
/** /**
* Returns a HashMap of message UID => ParsedMessageEnvelope * Returns a Map of message UID => ParsedMessageEnvelope
*/ */
public HashMap<String, ParsedMessageEnvelope> getMessageEnvelopes() { public Map<String, ParsedMessageEnvelope> getMessageEnvelopes() {
HashMap<String, ParsedMessageEnvelope> envelopes = new HashMap<String, ParsedMessageEnvelope>(); Map<String, ParsedMessageEnvelope> envelopes = new HashMap<String, ParsedMessageEnvelope>();
for (String uid : mData.keySet()) { for (String uid : mData.keySet()) {
ParsedMessageEnvelope envelope = new ParsedMessageEnvelope(); ParsedMessageEnvelope envelope = new ParsedMessageEnvelope();
HashMap<String, String> data = mData.get(uid); Map<String, String> data = mData.get(uid);
if (data != null) { if (data != null) {
for (Map.Entry<String, String> entry : data.entrySet()) { for (Map.Entry<String, String> entry : data.entrySet()) {
@ -2410,13 +2373,10 @@ public class WebDavStore extends Store {
*/ */
try { try {
if (length > 3) { if (length > 3) {
end = java.net.URLDecoder.decode(end, "UTF-8"); end = UrlEncodingHelper.decodeUtf8(end);
end = java.net.URLEncoder.encode(end, "UTF-8"); end = UrlEncodingHelper.encodeUtf8(end);
end = end.replaceAll("\\+", "%20"); end = end.replaceAll("\\+", "%20");
} }
} catch (UnsupportedEncodingException uee) {
Log.e(K9.LOG_TAG, "UnsupportedEncodingException caught in HttpGeneric(String uri): " + uee
+ "\nTrace: " + processException(uee));
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
Log.e(K9.LOG_TAG, "IllegalArgumentException caught in HttpGeneric(String uri): " + iae + "\nTrace: " Log.e(K9.LOG_TAG, "IllegalArgumentException caught in HttpGeneric(String uri): " + iae + "\nTrace: "
+ processException(iae)); + processException(iae));

View File

@ -48,7 +48,7 @@ public class ImapUtility {
* list is returned. * list is returned.
*/ */
public static List<String> getImapSequenceValues(String set) { public static List<String> getImapSequenceValues(String set) {
ArrayList<String> list = new ArrayList<String>(); List<String> list = new ArrayList<String>();
if (set != null) { if (set != null) {
String[] setItems = set.split(","); String[] setItems = set.split(",");
for (String item : setItems) { for (String item : setItems) {
@ -83,7 +83,7 @@ public class ImapUtility {
* is returned. * is returned.
*/ */
public static List<String> getImapRangeValues(String range) { public static List<String> getImapRangeValues(String range) {
ArrayList<String> list = new ArrayList<String>(); List<String> list = new ArrayList<String>();
try { try {
if (range != null) { if (range != null) {
int colonPos = range.indexOf(':'); int colonPos = range.indexOf(':');

View File

@ -0,0 +1,34 @@
package com.fsck.k9.mail.store.local;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.util.MimeUtil;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeMessage;
public class AttachmentMessageBodyUtil {
public static void writeTo(BinaryAttachmentBody body, OutputStream out) throws IOException,
MessagingException {
InputStream in = body.getInputStream();
try {
if (MimeUtil.ENC_7BIT.equalsIgnoreCase(body.getEncoding())) {
/*
* If we knew the message was already 7bit clean, then it
* could be sent along without processing. But since we
* don't know, we recursively parse it.
*/
MimeMessage message = new MimeMessage(in, true);
message.setUsing7bitTransport();
message.writeTo(out);
} else {
IOUtils.copy(in, out);
}
} finally {
in.close();
}
}
}

View File

@ -0,0 +1,58 @@
package com.fsck.k9.mail.store.local;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
import org.apache.james.mime4j.util.MimeUtil;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.filter.Base64OutputStream;
/**
* Superclass for attachments that contain binary data.
* The source for the data differs for the subclasses.
*/
public abstract class BinaryAttachmentBody implements Body {
protected String mEncoding;
@Override
public abstract InputStream getInputStream() throws MessagingException;
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
InputStream in = getInputStream();
try {
boolean closeStream = false;
if (MimeUtil.isBase64Encoding(mEncoding)) {
out = new Base64OutputStream(out);
closeStream = true;
} else if (MimeUtil.isQuotedPrintableEncoded(mEncoding)){
out = new QuotedPrintableOutputStream(out, false);
closeStream = true;
}
try {
IOUtils.copy(in, out);
} finally {
if (closeStream) {
out.close();
}
}
} finally {
in.close();
}
}
@Override
public void setEncoding(String encoding) throws MessagingException {
mEncoding = encoding;
}
public String getEncoding() {
return mEncoding;
}
}

View File

@ -0,0 +1,40 @@
package com.fsck.k9.mail.store.local;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import android.app.Application;
import android.net.Uri;
import com.fsck.k9.mail.MessagingException;
/**
* An attachment whose contents are loaded from an URI.
*/
public class LocalAttachmentBody extends BinaryAttachmentBody {
private Application mApplication;
private Uri mUri;
public LocalAttachmentBody(Uri uri, Application application) {
mApplication = application;
mUri = uri;
}
@Override
public InputStream getInputStream() throws MessagingException {
try {
return mApplication.getContentResolver().openInputStream(mUri);
} catch (FileNotFoundException fnfe) {
/*
* Since it's completely normal for us to try to serve up attachments that
* have been blown away, we just return an empty stream.
*/
return new ByteArrayInputStream(LocalStore.EMPTY_BYTE_ARRAY);
}
}
public Uri getContentUri() {
return mUri;
}
}

View File

@ -0,0 +1,31 @@
package com.fsck.k9.mail.store.local;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeBodyPart;
public class LocalAttachmentBodyPart extends MimeBodyPart {
private long mAttachmentId = -1;
public LocalAttachmentBodyPart(Body body, long attachmentId) throws MessagingException {
super(body);
mAttachmentId = attachmentId;
}
/**
* Returns the local attachment id of this body, or -1 if it is not stored.
* @return
*/
public long getAttachmentId() {
return mAttachmentId;
}
public void setAttachmentId(long attachmentId) {
mAttachmentId = attachmentId;
}
@Override
public String toString() {
return "" + mAttachmentId;
}
}

View File

@ -0,0 +1,49 @@
package com.fsck.k9.mail.store.local;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.james.mime4j.util.MimeUtil;
import android.app.Application;
import android.net.Uri;
import com.fsck.k9.mail.CompositeBody;
import com.fsck.k9.mail.MessagingException;
/**
* A {@link LocalAttachmentBody} extension containing a message/rfc822 type body
*
*/
public class LocalAttachmentMessageBody extends LocalAttachmentBody implements CompositeBody {
public LocalAttachmentMessageBody(Uri uri, Application application) {
super(uri, application);
}
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
AttachmentMessageBodyUtil.writeTo(this, out);
}
@Override
public void setUsing7bitTransport() throws MessagingException {
/*
* There's nothing to recurse into here, so there's nothing to do.
* The enclosing BodyPart already called setEncoding(MimeUtil.ENC_7BIT). Once
* writeTo() is called, the file with the rfc822 body will be opened
* for reading and will then be recursed.
*/
}
@Override
public void setEncoding(String encoding) throws MessagingException {
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
throw new MessagingException(
"Incompatible content-transfer-encoding applied to a CompositeBody");
}
mEncoding = encoding;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,560 @@
package com.fsck.k9.mail.store.local;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
public class LocalMessage extends MimeMessage {
private final LocalStore localStore;
long mId;
private int mAttachmentCount;
private String mSubject;
private String mPreview = "";
private boolean mHeadersLoaded = false;
private boolean mMessageDirty = false;
private long mThreadId;
private long mRootId;
private LocalMessage(LocalStore localStore) {
this.localStore = localStore;
}
LocalMessage(LocalStore localStore, String uid, Folder folder) {
this.localStore = localStore;
this.mUid = uid;
this.mFolder = folder;
}
void populateFromGetMessageCursor(Cursor cursor)
throws MessagingException {
final String subject = cursor.getString(0);
this.setSubject(subject == null ? "" : subject);
Address[] from = Address.unpack(cursor.getString(1));
if (from.length > 0) {
this.setFrom(from[0]);
}
this.setInternalSentDate(new Date(cursor.getLong(2)));
this.setUid(cursor.getString(3));
String flagList = cursor.getString(4);
if (flagList != null && flagList.length() > 0) {
String[] flags = flagList.split(",");
for (String flag : flags) {
try {
this.setFlagInternal(Flag.valueOf(flag), true);
}
catch (Exception e) {
if (!"X_BAD_FLAG".equals(flag)) {
Log.w(K9.LOG_TAG, "Unable to parse flag " + flag);
}
}
}
}
this.mId = cursor.getLong(5);
this.setRecipients(RecipientType.TO, Address.unpack(cursor.getString(6)));
this.setRecipients(RecipientType.CC, Address.unpack(cursor.getString(7)));
this.setRecipients(RecipientType.BCC, Address.unpack(cursor.getString(8)));
this.setReplyTo(Address.unpack(cursor.getString(9)));
this.mAttachmentCount = cursor.getInt(10);
this.setInternalDate(new Date(cursor.getLong(11)));
this.setMessageId(cursor.getString(12));
final String preview = cursor.getString(14);
mPreview = (preview == null ? "" : preview);
if (this.mFolder == null) {
LocalFolder f = new LocalFolder(this.localStore, cursor.getInt(13));
f.open(LocalFolder.OPEN_MODE_RW);
this.mFolder = f;
}
mThreadId = (cursor.isNull(15)) ? -1 : cursor.getLong(15);
mRootId = (cursor.isNull(16)) ? -1 : cursor.getLong(16);
boolean deleted = (cursor.getInt(17) == 1);
boolean read = (cursor.getInt(18) == 1);
boolean flagged = (cursor.getInt(19) == 1);
boolean answered = (cursor.getInt(20) == 1);
boolean forwarded = (cursor.getInt(21) == 1);
setFlagInternal(Flag.DELETED, deleted);
setFlagInternal(Flag.SEEN, read);
setFlagInternal(Flag.FLAGGED, flagged);
setFlagInternal(Flag.ANSWERED, answered);
setFlagInternal(Flag.FORWARDED, forwarded);
}
/**
* Fetch the message text for display. This always returns an HTML-ified version of the
* message, even if it was originally a text-only message.
* @return HTML version of message for display purposes or null.
* @throws MessagingException
*/
public String getTextForDisplay() throws MessagingException {
String text = null; // First try and fetch an HTML part.
Part part = MimeUtility.findFirstPartByMimeType(this, "text/html");
if (part == null) {
// If that fails, try and get a text part.
part = MimeUtility.findFirstPartByMimeType(this, "text/plain");
if (part != null && part.getBody() instanceof LocalTextBody) {
text = ((LocalTextBody) part.getBody()).getBodyForDisplay();
}
} else {
// We successfully found an HTML part; do the necessary character set decoding.
text = MimeUtility.getTextFromPart(part);
}
return text;
}
/* Custom version of writeTo that updates the MIME message based on localMessage
* changes.
*/
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
if (mMessageDirty) buildMimeRepresentation();
super.writeTo(out);
}
void buildMimeRepresentation() throws MessagingException {
if (!mMessageDirty) {
return;
}
super.setSubject(mSubject);
if (this.mFrom != null && this.mFrom.length > 0) {
super.setFrom(this.mFrom[0]);
}
super.setReplyTo(mReplyTo);
super.setSentDate(this.getSentDate());
super.setRecipients(RecipientType.TO, mTo);
super.setRecipients(RecipientType.CC, mCc);
super.setRecipients(RecipientType.BCC, mBcc);
if (mMessageId != null) super.setMessageId(mMessageId);
mMessageDirty = false;
}
@Override
public String getPreview() {
return mPreview;
}
@Override
public String getSubject() {
return mSubject;
}
@Override
public void setSubject(String subject) throws MessagingException {
mSubject = subject;
mMessageDirty = true;
}
@Override
public void setMessageId(String messageId) {
mMessageId = messageId;
mMessageDirty = true;
}
@Override
public boolean hasAttachments() {
return (mAttachmentCount > 0);
}
public int getAttachmentCount() {
return mAttachmentCount;
}
@Override
public void setFrom(Address from) throws MessagingException {
this.mFrom = new Address[] { from };
mMessageDirty = true;
}
@Override
public void setReplyTo(Address[] replyTo) throws MessagingException {
if (replyTo == null || replyTo.length == 0) {
mReplyTo = null;
} else {
mReplyTo = replyTo;
}
mMessageDirty = true;
}
/*
* For performance reasons, we add headers instead of setting them (see super implementation)
* which removes (expensive) them before adding them
*/
@Override
public void setRecipients(RecipientType type, Address[] addresses) throws MessagingException {
if (type == RecipientType.TO) {
if (addresses == null || addresses.length == 0) {
this.mTo = null;
} else {
this.mTo = addresses;
}
} else if (type == RecipientType.CC) {
if (addresses == null || addresses.length == 0) {
this.mCc = null;
} else {
this.mCc = addresses;
}
} else if (type == RecipientType.BCC) {
if (addresses == null || addresses.length == 0) {
this.mBcc = null;
} else {
this.mBcc = addresses;
}
} else {
throw new MessagingException("Unrecognized recipient type.");
}
mMessageDirty = true;
}
public void setFlagInternal(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set);
}
@Override
public long getId() {
return mId;
}
@Override
public void setFlag(final Flag flag, final boolean set) throws MessagingException {
try {
this.localStore.database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
try {
if (flag == Flag.DELETED && set) {
delete();
}
LocalMessage.super.setFlag(flag, set);
} catch (MessagingException e) {
throw new WrappedException(e);
}
/*
* Set the flags on the message.
*/
ContentValues cv = new ContentValues();
cv.put("flags", LocalMessage.this.localStore.serializeFlags(getFlags()));
cv.put("read", isSet(Flag.SEEN) ? 1 : 0);
cv.put("flagged", isSet(Flag.FLAGGED) ? 1 : 0);
cv.put("answered", isSet(Flag.ANSWERED) ? 1 : 0);
cv.put("forwarded", isSet(Flag.FORWARDED) ? 1 : 0);
db.update("messages", cv, "id = ?", new String[] { Long.toString(mId) });
return null;
}
});
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
this.localStore.notifyChange();
}
/*
* If a message is being marked as deleted we want to clear out it's content
* and attachments as well. Delete will not actually remove the row since we need
* to retain the uid for synchronization purposes.
*/
private void delete() throws MessagingException
{
/*
* Delete all of the message's content to save space.
*/
try {
this.localStore.database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
UnavailableStorageException {
String[] idArg = new String[] { Long.toString(mId) };
ContentValues cv = new ContentValues();
cv.put("deleted", 1);
cv.put("empty", 1);
cv.putNull("subject");
cv.putNull("sender_list");
cv.putNull("date");
cv.putNull("to_list");
cv.putNull("cc_list");
cv.putNull("bcc_list");
cv.putNull("preview");
cv.putNull("html_content");
cv.putNull("text_content");
cv.putNull("reply_to_list");
db.update("messages", cv, "id = ?", idArg);
/*
* Delete all of the message's attachments to save space.
* We do this explicit deletion here because we're not deleting the record
* in messages, which means our ON DELETE trigger for messages won't cascade
*/
try {
((LocalFolder) mFolder).deleteAttachments(mId);
} catch (MessagingException e) {
throw new WrappedException(e);
}
db.delete("attachments", "message_id = ?", idArg);
return null;
}
});
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
((LocalFolder)mFolder).deleteHeaders(mId);
this.localStore.notifyChange();
}
/*
* Completely remove a message from the local database
*
* TODO: document how this updates the thread structure
*/
@Override
public void destroy() throws MessagingException {
try {
this.localStore.database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
UnavailableStorageException {
try {
LocalFolder localFolder = (LocalFolder) mFolder;
localFolder.deleteAttachments(mId);
if (hasThreadChildren(db, mId)) {
// This message has children in the thread structure so we need to
// make it an empty message.
ContentValues cv = new ContentValues();
cv.put("id", mId);
cv.put("folder_id", localFolder.getId());
cv.put("deleted", 0);
cv.put("message_id", getMessageId());
cv.put("empty", 1);
db.replace("messages", null, cv);
// Nothing else to do
return null;
}
// Get the message ID of the parent message if it's empty
long currentId = getEmptyThreadParent(db, mId);
// Delete the placeholder message
deleteMessageRow(db, mId);
/*
* Walk the thread tree to delete all empty parents without children
*/
while (currentId != -1) {
if (hasThreadChildren(db, currentId)) {
// We made sure there are no empty leaf nodes and can stop now.
break;
}
// Get ID of the (empty) parent for the next iteration
long newId = getEmptyThreadParent(db, currentId);
// Delete the empty message
deleteMessageRow(db, currentId);
currentId = newId;
}
} catch (MessagingException e) {
throw new WrappedException(e);
}
return null;
}
});
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
this.localStore.notifyChange();
}
/**
* Get ID of the the given message's parent if the parent is an empty message.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to get the parent for.
*
* @return Message ID of the parent message if there exists a parent and it is empty.
* Otherwise {@code -1}.
*/
private long getEmptyThreadParent(SQLiteDatabase db, long messageId) {
Cursor cursor = db.rawQuery(
"SELECT m.id " +
"FROM threads t1 " +
"JOIN threads t2 ON (t1.parent = t2.id) " +
"LEFT JOIN messages m ON (t2.message_id = m.id) " +
"WHERE t1.message_id = ? AND m.empty = 1",
new String[] { Long.toString(messageId) });
try {
return (cursor.moveToFirst() && !cursor.isNull(0)) ? cursor.getLong(0) : -1;
} finally {
cursor.close();
}
}
/**
* Check whether or not a message has child messages in the thread structure.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to get the children for.
*
* @return {@code true} if the message has children. {@code false} otherwise.
*/
private boolean hasThreadChildren(SQLiteDatabase db, long messageId) {
Cursor cursor = db.rawQuery(
"SELECT COUNT(t2.id) " +
"FROM threads t1 " +
"JOIN threads t2 ON (t2.parent = t1.id) " +
"WHERE t1.message_id = ?",
new String[] { Long.toString(messageId) });
try {
return (cursor.moveToFirst() && !cursor.isNull(0) && cursor.getLong(0) > 0L);
} finally {
cursor.close();
}
}
/**
* Delete a message from the 'messages' and 'threads' tables.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to delete.
*/
private void deleteMessageRow(SQLiteDatabase db, long messageId) {
String[] idArg = { Long.toString(messageId) };
// Delete the message
db.delete("messages", "id = ?", idArg);
// Delete row in 'threads' table
// TODO: create trigger for 'messages' table to get rid of the row in 'threads' table
db.delete("threads", "message_id = ?", idArg);
}
private void loadHeaders() throws UnavailableStorageException {
List<LocalMessage> messages = new ArrayList<LocalMessage>();
messages.add(this);
mHeadersLoaded = true; // set true before calling populate headers to stop recursion
((LocalFolder) mFolder).populateHeaders(messages);
}
@Override
public void addHeader(String name, String value) throws UnavailableStorageException {
if (!mHeadersLoaded)
loadHeaders();
super.addHeader(name, value);
}
@Override
public void setHeader(String name, String value) throws UnavailableStorageException {
if (!mHeadersLoaded)
loadHeaders();
super.setHeader(name, value);
}
@Override
public String[] getHeader(String name) throws UnavailableStorageException {
if (!mHeadersLoaded)
loadHeaders();
return super.getHeader(name);
}
@Override
public void removeHeader(String name) throws UnavailableStorageException {
if (!mHeadersLoaded)
loadHeaders();
super.removeHeader(name);
}
@Override
public Set<String> getHeaderNames() throws UnavailableStorageException {
if (!mHeadersLoaded)
loadHeaders();
return super.getHeaderNames();
}
@Override
public LocalMessage clone() {
LocalMessage message = new LocalMessage(this.localStore);
super.copy(message);
message.mId = mId;
message.mAttachmentCount = mAttachmentCount;
message.mSubject = mSubject;
message.mPreview = mPreview;
message.mHeadersLoaded = mHeadersLoaded;
message.mMessageDirty = mMessageDirty;
return message;
}
public long getThreadId() {
return mThreadId;
}
public long getRootId() {
return mRootId;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
package com.fsck.k9.mail.store.local;
import com.fsck.k9.mail.internet.TextBody;
public class LocalTextBody extends TextBody {
/**
* This is an HTML-ified version of the message for display purposes.
*/
private final String mBodyForDisplay;
public LocalTextBody(String body, String bodyForDisplay) {
super(body);
this.mBodyForDisplay = bodyForDisplay;
}
public String getBodyForDisplay() {
return mBodyForDisplay;
}
}//LocalTextBody

View File

@ -0,0 +1,599 @@
package com.fsck.k9.mail.store.local;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import android.content.ContentValues;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.LockableDatabase;
import com.fsck.k9.provider.AttachmentProvider;
class StoreSchemaDefinition implements LockableDatabase.SchemaDefinition {
/**
*
*/
private final LocalStore localStore;
/**
* @param localStore
*/
StoreSchemaDefinition(LocalStore localStore) {
this.localStore = localStore;
}
@Override
public int getVersion() {
return LocalStore.DB_VERSION;
}
@Override
public void doDbUpgrade(final SQLiteDatabase db) {
try {
upgradeDatabase(db);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Exception while upgrading database. Resetting the DB to v0", e);
db.setVersion(0);
upgradeDatabase(db);
}
}
private void upgradeDatabase(final SQLiteDatabase db) {
Log.i(K9.LOG_TAG, String.format(Locale.US, "Upgrading database from version %d to version %d",
db.getVersion(), LocalStore.DB_VERSION));
AttachmentProvider.clear(this.localStore.mApplication);
db.beginTransaction();
try {
// schema version 29 was when we moved to incremental updates
// in the case of a new db or a < v29 db, we blow away and start from scratch
if (db.getVersion() < 29) {
db.execSQL("DROP TABLE IF EXISTS folders");
db.execSQL("CREATE TABLE folders (id INTEGER PRIMARY KEY, name TEXT, "
+ "last_updated INTEGER, unread_count INTEGER, visible_limit INTEGER, status TEXT, "
+ "push_state TEXT, last_pushed INTEGER, flagged_count INTEGER default 0, "
+ "integrate INTEGER, top_group INTEGER, poll_class TEXT, push_class TEXT, display_class TEXT, notify_class TEXT"
+ ")");
db.execSQL("CREATE INDEX IF NOT EXISTS folder_name ON folders (name)");
db.execSQL("DROP TABLE IF EXISTS messages");
db.execSQL("CREATE TABLE messages (" +
"id INTEGER PRIMARY KEY, " +
"deleted INTEGER default 0, " +
"folder_id INTEGER, " +
"uid TEXT, " +
"subject TEXT, " +
"date INTEGER, " +
"flags TEXT, " +
"sender_list TEXT, " +
"to_list TEXT, " +
"cc_list TEXT, " +
"bcc_list TEXT, " +
"reply_to_list TEXT, " +
"html_content TEXT, " +
"text_content TEXT, " +
"attachment_count INTEGER, " +
"internal_date INTEGER, " +
"message_id TEXT, " +
"preview TEXT, " +
"mime_type TEXT, "+
"normalized_subject_hash INTEGER, " +
"empty INTEGER, " +
"read INTEGER default 0, " +
"flagged INTEGER default 0, " +
"answered INTEGER default 0, " +
"forwarded INTEGER default 0" +
")");
db.execSQL("DROP TABLE IF EXISTS headers");
db.execSQL("CREATE TABLE headers (id INTEGER PRIMARY KEY, message_id INTEGER, name TEXT, value TEXT)");
db.execSQL("CREATE INDEX IF NOT EXISTS header_folder ON headers (message_id)");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_uid ON messages (uid, folder_id)");
db.execSQL("DROP INDEX IF EXISTS msg_folder_id");
db.execSQL("DROP INDEX IF EXISTS msg_folder_id_date");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_folder_id_deleted_date ON messages (folder_id,deleted,internal_date)");
db.execSQL("DROP INDEX IF EXISTS msg_empty");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_empty ON messages (empty)");
db.execSQL("DROP INDEX IF EXISTS msg_read");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_read ON messages (read)");
db.execSQL("DROP INDEX IF EXISTS msg_flagged");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_flagged ON messages (flagged)");
db.execSQL("DROP INDEX IF EXISTS msg_composite");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_composite ON messages (deleted, empty,folder_id,flagged,read)");
db.execSQL("DROP TABLE IF EXISTS threads");
db.execSQL("CREATE TABLE threads (" +
"id INTEGER PRIMARY KEY, " +
"message_id INTEGER, " +
"root INTEGER, " +
"parent INTEGER" +
")");
db.execSQL("DROP INDEX IF EXISTS threads_message_id");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_message_id ON threads (message_id)");
db.execSQL("DROP INDEX IF EXISTS threads_root");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_root ON threads (root)");
db.execSQL("DROP INDEX IF EXISTS threads_parent");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_parent ON threads (parent)");
db.execSQL("DROP TRIGGER IF EXISTS set_thread_root");
db.execSQL("CREATE TRIGGER set_thread_root " +
"AFTER INSERT ON threads " +
"BEGIN " +
"UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
"END");
db.execSQL("DROP TABLE IF EXISTS attachments");
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
+ "store_data TEXT, content_uri TEXT, size INTEGER, name TEXT,"
+ "mime_type TEXT, content_id TEXT, content_disposition TEXT)");
db.execSQL("DROP TABLE IF EXISTS pending_commands");
db.execSQL("CREATE TABLE pending_commands " +
"(id INTEGER PRIMARY KEY, command TEXT, arguments TEXT)");
db.execSQL("DROP TRIGGER IF EXISTS delete_folder");
db.execSQL("CREATE TRIGGER delete_folder BEFORE DELETE ON folders BEGIN DELETE FROM messages WHERE old.id = folder_id; END;");
db.execSQL("DROP TRIGGER IF EXISTS delete_message");
db.execSQL("CREATE TRIGGER delete_message BEFORE DELETE ON messages BEGIN DELETE FROM attachments WHERE old.id = message_id; "
+ "DELETE FROM headers where old.id = message_id; END;");
} else {
// in the case that we're starting out at 29 or newer, run all the needed updates
if (db.getVersion() < 30) {
try {
db.execSQL("ALTER TABLE messages ADD deleted INTEGER default 0");
} catch (SQLiteException e) {
if (! e.toString().startsWith("duplicate column name: deleted")) {
throw e;
}
}
}
if (db.getVersion() < 31) {
db.execSQL("DROP INDEX IF EXISTS msg_folder_id_date");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_folder_id_deleted_date ON messages (folder_id,deleted,internal_date)");
}
if (db.getVersion() < 32) {
db.execSQL("UPDATE messages SET deleted = 1 WHERE flags LIKE '%DELETED%'");
}
if (db.getVersion() < 33) {
try {
db.execSQL("ALTER TABLE messages ADD preview TEXT");
} catch (SQLiteException e) {
if (! e.toString().startsWith("duplicate column name: preview")) {
throw e;
}
}
}
if (db.getVersion() < 34) {
try {
db.execSQL("ALTER TABLE folders ADD flagged_count INTEGER default 0");
} catch (SQLiteException e) {
if (! e.getMessage().startsWith("duplicate column name: flagged_count")) {
throw e;
}
}
}
if (db.getVersion() < 35) {
try {
db.execSQL("update messages set flags = replace(flags, 'X_NO_SEEN_INFO', 'X_BAD_FLAG')");
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Unable to get rid of obsolete flag X_NO_SEEN_INFO", e);
}
}
if (db.getVersion() < 36) {
try {
db.execSQL("ALTER TABLE attachments ADD content_id TEXT");
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Unable to add content_id column to attachments");
}
}
if (db.getVersion() < 37) {
try {
db.execSQL("ALTER TABLE attachments ADD content_disposition TEXT");
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Unable to add content_disposition column to attachments");
}
}
// Database version 38 is solely to prune cached attachments now that we clear them better
if (db.getVersion() < 39) {
try {
db.execSQL("DELETE FROM headers WHERE id in (SELECT headers.id FROM headers LEFT JOIN messages ON headers.message_id = messages.id WHERE messages.id IS NULL)");
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Unable to remove extra header data from the database");
}
}
// V40: Store the MIME type for a message.
if (db.getVersion() < 40) {
try {
db.execSQL("ALTER TABLE messages ADD mime_type TEXT");
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Unable to add mime_type column to messages");
}
}
if (db.getVersion() < 41) {
try {
db.execSQL("ALTER TABLE folders ADD integrate INTEGER");
db.execSQL("ALTER TABLE folders ADD top_group INTEGER");
db.execSQL("ALTER TABLE folders ADD poll_class TEXT");
db.execSQL("ALTER TABLE folders ADD push_class TEXT");
db.execSQL("ALTER TABLE folders ADD display_class TEXT");
} catch (SQLiteException e) {
if (! e.getMessage().startsWith("duplicate column name:")) {
throw e;
}
}
Cursor cursor = null;
try {
SharedPreferences prefs = this.localStore.getPreferences();
cursor = db.rawQuery("SELECT id, name FROM folders", null);
while (cursor.moveToNext()) {
try {
int id = cursor.getInt(0);
String name = cursor.getString(1);
update41Metadata(db, prefs, id, name);
} catch (Exception e) {
Log.e(K9.LOG_TAG, " error trying to ugpgrade a folder class", e);
}
}
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Exception while upgrading database to v41. folder classes may have vanished", e);
} finally {
Utility.closeQuietly(cursor);
}
}
if (db.getVersion() == 41) {
try {
long startTime = System.currentTimeMillis();
SharedPreferences.Editor editor = this.localStore.getPreferences().edit();
List <? extends Folder > folders = this.localStore.getPersonalNamespaces(true);
for (Folder folder : folders) {
if (folder instanceof LocalFolder) {
LocalFolder lFolder = (LocalFolder)folder;
lFolder.save(editor);
}
}
editor.commit();
long endTime = System.currentTimeMillis();
Log.i(K9.LOG_TAG, "Putting folder preferences for " + folders.size() + " folders back into Preferences took " + (endTime - startTime) + " ms");
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Could not replace Preferences in upgrade from DB_VERSION 41", e);
}
}
if (db.getVersion() < 43) {
try {
// If folder "OUTBOX" (old, v3.800 - v3.802) exists, rename it to
// "K9MAIL_INTERNAL_OUTBOX" (new)
LocalFolder oldOutbox = new LocalFolder(this.localStore, "OUTBOX");
if (oldOutbox.exists()) {
ContentValues cv = new ContentValues();
cv.put("name", Account.OUTBOX);
db.update("folders", cv, "name = ?", new String[] { "OUTBOX" });
Log.i(K9.LOG_TAG, "Renamed folder OUTBOX to " + Account.OUTBOX);
}
// Check if old (pre v3.800) localized outbox folder exists
String localizedOutbox = K9.app.getString(R.string.special_mailbox_name_outbox);
LocalFolder obsoleteOutbox = new LocalFolder(this.localStore, localizedOutbox);
if (obsoleteOutbox.exists()) {
// Get all messages from the localized outbox ...
List<? extends Message> messages = obsoleteOutbox.getMessages(null, false);
if (messages.size() > 0) {
// ... and move them to the drafts folder (we don't want to
// surprise the user by sending potentially very old messages)
LocalFolder drafts = new LocalFolder(this.localStore, this.localStore.getAccount().getDraftsFolderName());
obsoleteOutbox.moveMessages(messages, drafts);
}
// Now get rid of the localized outbox
obsoleteOutbox.delete();
obsoleteOutbox.delete(true);
}
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Error trying to fix the outbox folders", e);
}
}
if (db.getVersion() < 44) {
try {
db.execSQL("ALTER TABLE messages ADD thread_root INTEGER");
db.execSQL("ALTER TABLE messages ADD thread_parent INTEGER");
db.execSQL("ALTER TABLE messages ADD normalized_subject_hash INTEGER");
db.execSQL("ALTER TABLE messages ADD empty INTEGER");
} catch (SQLiteException e) {
if (! e.getMessage().startsWith("duplicate column name:")) {
throw e;
}
}
}
if (db.getVersion() < 45) {
try {
db.execSQL("DROP INDEX IF EXISTS msg_empty");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_empty ON messages (empty)");
db.execSQL("DROP INDEX IF EXISTS msg_thread_root");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_thread_root ON messages (thread_root)");
db.execSQL("DROP INDEX IF EXISTS msg_thread_parent");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_thread_parent ON messages (thread_parent)");
} catch (SQLiteException e) {
if (! e.getMessage().startsWith("duplicate column name:")) {
throw e;
}
}
}
if (db.getVersion() < 46) {
db.execSQL("ALTER TABLE messages ADD read INTEGER default 0");
db.execSQL("ALTER TABLE messages ADD flagged INTEGER default 0");
db.execSQL("ALTER TABLE messages ADD answered INTEGER default 0");
db.execSQL("ALTER TABLE messages ADD forwarded INTEGER default 0");
String[] projection = { "id", "flags" };
ContentValues cv = new ContentValues();
List<Flag> extraFlags = new ArrayList<Flag>();
Cursor cursor = db.query("messages", projection, null, null, null, null, null);
try {
while (cursor.moveToNext()) {
long id = cursor.getLong(0);
String flagList = cursor.getString(1);
boolean read = false;
boolean flagged = false;
boolean answered = false;
boolean forwarded = false;
if (flagList != null && flagList.length() > 0) {
String[] flags = flagList.split(",");
for (String flagStr : flags) {
try {
Flag flag = Flag.valueOf(flagStr);
switch (flag) {
case ANSWERED: {
answered = true;
break;
}
case DELETED: {
// Don't store this in column 'flags'
break;
}
case FLAGGED: {
flagged = true;
break;
}
case FORWARDED: {
forwarded = true;
break;
}
case SEEN: {
read = true;
break;
}
case DRAFT:
case RECENT:
case X_DESTROYED:
case X_DOWNLOADED_FULL:
case X_DOWNLOADED_PARTIAL:
case X_GOT_ALL_HEADERS:
case X_REMOTE_COPY_STARTED:
case X_SEND_FAILED:
case X_SEND_IN_PROGRESS: {
extraFlags.add(flag);
break;
}
}
} catch (Exception e) {
// Ignore bad flags
}
}
}
cv.put("flags", this.localStore.serializeFlags(extraFlags));
cv.put("read", read);
cv.put("flagged", flagged);
cv.put("answered", answered);
cv.put("forwarded", forwarded);
db.update("messages", cv, "id = ?", new String[] { Long.toString(id) });
cv.clear();
extraFlags.clear();
}
} finally {
cursor.close();
}
db.execSQL("CREATE INDEX IF NOT EXISTS msg_read ON messages (read)");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_flagged ON messages (flagged)");
}
if (db.getVersion() < 47) {
// Create new 'threads' table
db.execSQL("DROP TABLE IF EXISTS threads");
db.execSQL("CREATE TABLE threads (" +
"id INTEGER PRIMARY KEY, " +
"message_id INTEGER, " +
"root INTEGER, " +
"parent INTEGER" +
")");
// Create indices for new table
db.execSQL("DROP INDEX IF EXISTS threads_message_id");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_message_id ON threads (message_id)");
db.execSQL("DROP INDEX IF EXISTS threads_root");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_root ON threads (root)");
db.execSQL("DROP INDEX IF EXISTS threads_parent");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_parent ON threads (parent)");
// Create entries for all messages in 'threads' table
db.execSQL("INSERT INTO threads (message_id) SELECT id FROM messages");
// Copy thread structure from 'messages' table to 'threads'
Cursor cursor = db.query("messages",
new String[] { "id", "thread_root", "thread_parent" },
null, null, null, null, null);
try {
ContentValues cv = new ContentValues();
while (cursor.moveToNext()) {
cv.clear();
long messageId = cursor.getLong(0);
if (!cursor.isNull(1)) {
long threadRootMessageId = cursor.getLong(1);
db.execSQL("UPDATE threads SET root = (SELECT t.id FROM " +
"threads t WHERE t.message_id = ?) " +
"WHERE message_id = ?",
new String[] {
Long.toString(threadRootMessageId),
Long.toString(messageId)
});
}
if (!cursor.isNull(2)) {
long threadParentMessageId = cursor.getLong(2);
db.execSQL("UPDATE threads SET parent = (SELECT t.id FROM " +
"threads t WHERE t.message_id = ?) " +
"WHERE message_id = ?",
new String[] {
Long.toString(threadParentMessageId),
Long.toString(messageId)
});
}
}
} finally {
cursor.close();
}
// Remove indices for old thread-related columns in 'messages' table
db.execSQL("DROP INDEX IF EXISTS msg_thread_root");
db.execSQL("DROP INDEX IF EXISTS msg_thread_parent");
// Clear out old thread-related columns in 'messages'
ContentValues cv = new ContentValues();
cv.putNull("thread_root");
cv.putNull("thread_parent");
db.update("messages", cv, null, null);
}
if (db.getVersion() < 48) {
db.execSQL("UPDATE threads SET root=id WHERE root IS NULL");
db.execSQL("CREATE TRIGGER set_thread_root " +
"AFTER INSERT ON threads " +
"BEGIN " +
"UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
"END");
}
if (db.getVersion() < 49) {
db.execSQL("CREATE INDEX IF NOT EXISTS msg_composite ON messages (deleted, empty,folder_id,flagged,read)");
}
if (db.getVersion() < 50) {
try {
db.execSQL("ALTER TABLE folders ADD notify_class TEXT default '" +
Folder.FolderClass.INHERITED.name() + "'");
} catch (SQLiteException e) {
if (! e.getMessage().startsWith("duplicate column name:")) {
throw e;
}
}
ContentValues cv = new ContentValues();
cv.put("notify_class", Folder.FolderClass.FIRST_CLASS.name());
db.update("folders", cv, "name = ?",
new String[] { this.localStore.getAccount().getInboxFolderName() });
}
}
db.setVersion(LocalStore.DB_VERSION);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (db.getVersion() != LocalStore.DB_VERSION) {
throw new RuntimeException("Database upgrade failed!");
}
}
private void update41Metadata(final SQLiteDatabase db, SharedPreferences prefs, int id, String name) {
Folder.FolderClass displayClass = Folder.FolderClass.NO_CLASS;
Folder.FolderClass syncClass = Folder.FolderClass.INHERITED;
Folder.FolderClass pushClass = Folder.FolderClass.SECOND_CLASS;
boolean inTopGroup = false;
boolean integrate = false;
if (this.localStore.getAccount().getInboxFolderName().equals(name)) {
displayClass = Folder.FolderClass.FIRST_CLASS;
syncClass = Folder.FolderClass.FIRST_CLASS;
pushClass = Folder.FolderClass.FIRST_CLASS;
inTopGroup = true;
integrate = true;
}
try {
displayClass = Folder.FolderClass.valueOf(prefs.getString(this.localStore.uUid + "." + name + ".displayMode", displayClass.name()));
syncClass = Folder.FolderClass.valueOf(prefs.getString(this.localStore.uUid + "." + name + ".syncMode", syncClass.name()));
pushClass = Folder.FolderClass.valueOf(prefs.getString(this.localStore.uUid + "." + name + ".pushMode", pushClass.name()));
inTopGroup = prefs.getBoolean(this.localStore.uUid + "." + name + ".inTopGroup", inTopGroup);
integrate = prefs.getBoolean(this.localStore.uUid + "." + name + ".integrate", integrate);
} catch (Exception e) {
Log.e(K9.LOG_TAG, " Throwing away an error while trying to upgrade folder metadata", e);
}
if (displayClass == Folder.FolderClass.NONE) {
displayClass = Folder.FolderClass.NO_CLASS;
}
if (syncClass == Folder.FolderClass.NONE) {
syncClass = Folder.FolderClass.INHERITED;
}
if (pushClass == Folder.FolderClass.NONE) {
pushClass = Folder.FolderClass.INHERITED;
}
db.execSQL("UPDATE folders SET integrate = ?, top_group = ?, poll_class=?, push_class =?, display_class = ? WHERE id = ?",
new Object[] { integrate, inTopGroup, syncClass, pushClass, displayClass, id });
}
}

View File

@ -0,0 +1,29 @@
package com.fsck.k9.mail.store.local;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import com.fsck.k9.mail.MessagingException;
/**
* An attachment whose contents are contained in a file.
*/
public class TempFileBody extends BinaryAttachmentBody {
private final File mFile;
public TempFileBody(String filename) {
mFile = new File(filename);
}
@Override
public InputStream getInputStream() throws MessagingException {
try {
return new FileInputStream(mFile);
} catch (FileNotFoundException e) {
return new ByteArrayInputStream(LocalStore.EMPTY_BYTE_ARRAY);
}
}
}

View File

@ -0,0 +1,40 @@
package com.fsck.k9.mail.store.local;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.james.mime4j.util.MimeUtil;
import com.fsck.k9.mail.CompositeBody;
import com.fsck.k9.mail.MessagingException;
/**
* An attachment containing a body of type message/rfc822
* whose contents are contained in a file.
*/
public class TempFileMessageBody extends TempFileBody implements CompositeBody {
public TempFileMessageBody(String filename) {
super(filename);
}
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
AttachmentMessageBodyUtil.writeTo(this, out);
}
@Override
public void setUsing7bitTransport() throws MessagingException {
// see LocalAttachmentMessageBody.setUsing7bitTransport()
}
@Override
public void setEncoding(String encoding) throws MessagingException {
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
throw new MessagingException(
"Incompatible content-transfer-encoding applied to a CompositeBody");
}
mEncoding = encoding;
}
}

View File

@ -0,0 +1,17 @@
package com.fsck.k9.mail.store.local;
class ThreadInfo {
public final long threadId;
public final long msgId;
public final String messageId;
public final long rootId;
public final long parentId;
public ThreadInfo(long threadId, long msgId, String messageId, long rootId, long parentId) {
this.threadId = threadId;
this.msgId = msgId;
this.messageId = messageId;
this.rootId = rootId;
this.parentId = parentId;
}
}

View File

@ -6,6 +6,7 @@ import android.util.Log;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*; import com.fsck.k9.mail.*;
import com.fsck.k9.mail.Message.RecipientType; import com.fsck.k9.mail.Message.RecipientType;
@ -14,7 +15,7 @@ import com.fsck.k9.mail.filter.LineWrapOutputStream;
import com.fsck.k9.mail.filter.PeekableInputStream; import com.fsck.k9.mail.filter.PeekableInputStream;
import com.fsck.k9.mail.filter.SmtpDataStuffing; import com.fsck.k9.mail.filter.SmtpDataStuffing;
import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore.LocalMessage; import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.net.ssl.TrustedSocketFactory; import com.fsck.k9.net.ssl.TrustedSocketFactory;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
@ -23,7 +24,6 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.*; import java.net.*;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.*; import java.util.*;
@ -92,29 +92,24 @@ public class SmtpTransport extends Transport {
} }
if (smtpUri.getUserInfo() != null) { if (smtpUri.getUserInfo() != null) {
try {
String[] userInfoParts = smtpUri.getUserInfo().split(":"); String[] userInfoParts = smtpUri.getUserInfo().split(":");
if (userInfoParts.length == 1) { if (userInfoParts.length == 1) {
authType = AuthType.PLAIN; authType = AuthType.PLAIN;
username = URLDecoder.decode(userInfoParts[0], "UTF-8"); username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
} else if (userInfoParts.length == 2) { } else if (userInfoParts.length == 2) {
authType = AuthType.PLAIN; authType = AuthType.PLAIN;
username = URLDecoder.decode(userInfoParts[0], "UTF-8"); username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
password = URLDecoder.decode(userInfoParts[1], "UTF-8"); password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
} else if (userInfoParts.length == 3) { } else if (userInfoParts.length == 3) {
// NOTE: In SmptTransport URIs, the authType comes last! // NOTE: In SmptTransport URIs, the authType comes last!
authType = AuthType.valueOf(userInfoParts[2]); authType = AuthType.valueOf(userInfoParts[2]);
username = URLDecoder.decode(userInfoParts[0], "UTF-8"); username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
if (authType == AuthType.EXTERNAL) { if (authType == AuthType.EXTERNAL) {
clientCertificateAlias = URLDecoder.decode(userInfoParts[1], "UTF-8"); clientCertificateAlias = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
} else { } else {
password = URLDecoder.decode(userInfoParts[1], "UTF-8"); password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
} }
} }
} catch (UnsupportedEncodingException enc) {
// This shouldn't happen since the encoding is hardcoded to UTF-8
throw new IllegalArgumentException("Couldn't urldecode username or password.", enc);
}
} }
return new ServerSettings(TRANSPORT_TYPE, host, port, connectionSecurity, return new ServerSettings(TRANSPORT_TYPE, host, port, connectionSecurity,
@ -133,20 +128,12 @@ public class SmtpTransport extends Transport {
* @see SmtpTransport#decodeUri(String) * @see SmtpTransport#decodeUri(String)
*/ */
public static String createUri(ServerSettings server) { public static String createUri(ServerSettings server) {
String userEnc; String userEnc = (server.username != null) ?
String passwordEnc; UrlEncodingHelper.encodeUtf8(server.username) : "";
String clientCertificateAliasEnc; String passwordEnc = (server.password != null) ?
try { UrlEncodingHelper.encodeUtf8(server.password) : "";
userEnc = (server.username != null) ? String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
URLEncoder.encode(server.username, "UTF-8") : ""; UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
passwordEnc = (server.password != null) ?
URLEncoder.encode(server.password, "UTF-8") : "";
clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
URLEncoder.encode(server.clientCertificateAlias, "UTF-8") : "";
}
catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Could not encode username or password", e);
}
String scheme; String scheme;
switch (server.connectionSecurity) { switch (server.connectionSecurity) {
@ -183,16 +170,16 @@ public class SmtpTransport extends Transport {
} }
String mHost; private String mHost;
int mPort; private int mPort;
String mUsername; private String mUsername;
String mPassword; private String mPassword;
String mClientCertificateAlias; private String mClientCertificateAlias;
AuthType mAuthType; private AuthType mAuthType;
ConnectionSecurity mConnectionSecurity; private ConnectionSecurity mConnectionSecurity;
Socket mSocket; private Socket mSocket;
PeekableInputStream mIn; private PeekableInputStream mIn;
OutputStream mOut; private OutputStream mOut;
private boolean m8bitEncodingAllowed; private boolean m8bitEncodingAllowed;
private int mLargestAcceptableMessage; private int mLargestAcceptableMessage;
@ -269,7 +256,7 @@ public class SmtpTransport extends Transport {
} }
} }
HashMap<String,String> extensions = sendHello(localHost); Map<String,String> extensions = sendHello(localHost);
m8bitEncodingAllowed = extensions.containsKey("8BITMIME"); m8bitEncodingAllowed = extensions.containsKey("8BITMIME");
@ -432,7 +419,7 @@ public class SmtpTransport extends Transport {
* @param host * @param host
* The EHLO/HELO parameter as defined by the RFC. * The EHLO/HELO parameter as defined by the RFC.
* *
* @return A (possibly empty) {@code HashMap<String,String>} of extensions (upper case) and * @return A (possibly empty) {@code Map<String,String>} of extensions (upper case) and
* their parameters (possibly 0 length) as returned by the EHLO command * their parameters (possibly 0 length) as returned by the EHLO command
* *
* @throws IOException * @throws IOException
@ -440,8 +427,8 @@ public class SmtpTransport extends Transport {
* @throws MessagingException * @throws MessagingException
* In case of a malformed response. * In case of a malformed response.
*/ */
private HashMap<String,String> sendHello(String host) throws IOException, MessagingException { private Map<String,String> sendHello(String host) throws IOException, MessagingException {
HashMap<String, String> extensions = new HashMap<String, String>(); Map<String, String> extensions = new HashMap<String, String>();
try { try {
List<String> results = executeSimpleCommand("EHLO " + host); List<String> results = executeSimpleCommand("EHLO " + host);
// Remove the EHLO greeting response // Remove the EHLO greeting response
@ -466,7 +453,7 @@ public class SmtpTransport extends Transport {
@Override @Override
public void sendMessage(Message message) throws MessagingException { public void sendMessage(Message message) throws MessagingException {
ArrayList<Address> addresses = new ArrayList<Address>(); List<Address> addresses = new ArrayList<Address>();
{ {
addresses.addAll(Arrays.asList(message.getRecipients(RecipientType.TO))); addresses.addAll(Arrays.asList(message.getRecipients(RecipientType.TO)));
addresses.addAll(Arrays.asList(message.getRecipients(RecipientType.CC))); addresses.addAll(Arrays.asList(message.getRecipients(RecipientType.CC)));
@ -474,12 +461,12 @@ public class SmtpTransport extends Transport {
} }
message.setRecipients(RecipientType.BCC, null); message.setRecipients(RecipientType.BCC, null);
HashMap<String, ArrayList<String>> charsetAddressesMap = Map<String, List<String>> charsetAddressesMap =
new HashMap<String, ArrayList<String>>(); new HashMap<String, List<String>>();
for (Address address : addresses) { for (Address address : addresses) {
String addressString = address.getAddress(); String addressString = address.getAddress();
String charset = MimeUtility.getCharsetFromAddress(addressString); String charset = MimeUtility.getCharsetFromAddress(addressString);
ArrayList<String> addressesOfCharset = charsetAddressesMap.get(charset); List<String> addressesOfCharset = charsetAddressesMap.get(charset);
if (addressesOfCharset == null) { if (addressesOfCharset == null) {
addressesOfCharset = new ArrayList<String>(); addressesOfCharset = new ArrayList<String>();
charsetAddressesMap.put(charset, addressesOfCharset); charsetAddressesMap.put(charset, addressesOfCharset);
@ -487,16 +474,16 @@ public class SmtpTransport extends Transport {
addressesOfCharset.add(addressString); addressesOfCharset.add(addressString);
} }
for (Map.Entry<String, ArrayList<String>> charsetAddressesMapEntry : for (Map.Entry<String, List<String>> charsetAddressesMapEntry :
charsetAddressesMap.entrySet()) { charsetAddressesMap.entrySet()) {
String charset = charsetAddressesMapEntry.getKey(); String charset = charsetAddressesMapEntry.getKey();
ArrayList<String> addressesOfCharset = charsetAddressesMapEntry.getValue(); List<String> addressesOfCharset = charsetAddressesMapEntry.getValue();
message.setCharset(charset); message.setCharset(charset);
sendMessageTo(addressesOfCharset, message); sendMessageTo(addressesOfCharset, message);
} }
} }
private void sendMessageTo(ArrayList<String> addresses, Message message) private void sendMessageTo(List<String> addresses, Message message)
throws MessagingException { throws MessagingException {
boolean possibleSend = false; boolean possibleSend = false;
@ -511,6 +498,7 @@ public class SmtpTransport extends Transport {
if (mLargestAcceptableMessage > 0 && ((LocalMessage)message).hasAttachments()) { if (mLargestAcceptableMessage > 0 && ((LocalMessage)message).hasAttachments()) {
if (message.calculateSize() > mLargestAcceptableMessage) { if (message.calculateSize() > mLargestAcceptableMessage) {
MessagingException me = new MessagingException("Message too large for server"); MessagingException me = new MessagingException("Message too large for server");
//TODO this looks rather suspicious... shouldn't it be true?
me.setPermanentFailure(possibleSend); me.setPermanentFailure(possibleSend);
throw me; throw me;
} }
@ -545,14 +533,13 @@ public class SmtpTransport extends Transport {
possibleSend = false; possibleSend = false;
} }
//TODO this looks rather suspicious... why is possibleSend used, and why are 5xx NOT permanent (in contrast to the log text)
me.setPermanentFailure(possibleSend); me.setPermanentFailure(possibleSend);
throw me; throw me;
} finally { } finally {
close(); close();
} }
} }
@Override @Override

View File

@ -11,6 +11,8 @@ import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Transport; import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.store.WebDavStore; import com.fsck.k9.mail.store.WebDavStore;
import java.util.Collections;
public class WebDavTransport extends Transport { public class WebDavTransport extends Transport {
public static final String TRANSPORT_TYPE = WebDavStore.STORE_TYPE; public static final String TRANSPORT_TYPE = WebDavStore.STORE_TYPE;
@ -66,6 +68,6 @@ public class WebDavTransport extends Transport {
@Override @Override
public void sendMessage(Message message) throws MessagingException { public void sendMessage(Message message) throws MessagingException {
store.sendMessages(new Message[] { message }); store.sendMessages(Collections.singletonList(message));
} }
} }

View File

@ -63,6 +63,8 @@ public final class TrustManagerFactory {
String message = null; String message = null;
X509Certificate certificate = chain[0]; X509Certificate certificate = chain[0];
Throwable cause = null;
try { try {
defaultTrustManager.checkServerTrusted(chain, authType); defaultTrustManager.checkServerTrusted(chain, authType);
new StrictHostnameVerifier().verify(mHost, certificate); new StrictHostnameVerifier().verify(mHost, certificate);
@ -70,15 +72,17 @@ public final class TrustManagerFactory {
} catch (CertificateException e) { } catch (CertificateException e) {
// cert. chain can't be validated // cert. chain can't be validated
message = e.getMessage(); message = e.getMessage();
cause = e;
} catch (SSLException e) { } catch (SSLException e) {
// host name doesn't match certificate // host name doesn't match certificate
message = e.getMessage(); message = e.getMessage();
cause = e;
} }
// Check the local key store if we couldn't verify the certificate using the global // Check the local key store if we couldn't verify the certificate using the global
// key store or if the host name doesn't match the certificate name // key store or if the host name doesn't match the certificate name
if (!keyStore.isValidCertificate(certificate, mHost, mPort)) { if (!keyStore.isValidCertificate(certificate, mHost, mPort)) {
throw new CertificateChainException(message, chain); throw new CertificateChainException(message, chain, cause);
} }
} }

View File

@ -5,14 +5,15 @@ import com.fsck.k9.K9;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
public class Editor implements android.content.SharedPreferences.Editor { public class Editor implements android.content.SharedPreferences.Editor {
private Storage storage; private Storage storage;
private HashMap<String, String> changes = new HashMap<String, String>(); private Map<String, String> changes = new HashMap<String, String>();
private ArrayList<String> removals = new ArrayList<String>(); private List<String> removals = new ArrayList<String>();
private boolean removeAll = false; private boolean removeAll = false;
Map<String, String> snapshot = new HashMap<String, String>(); Map<String, String> snapshot = new HashMap<String, String>();

View File

@ -51,7 +51,7 @@ public class Settings {
SortedMap<Integer, SettingsDescription> headMap = versions.headMap(version + 1); SortedMap<Integer, SettingsDescription> headMap = versions.headMap(version + 1);
// Skip this setting if it was introduced after 'version' // Skip this setting if it was introduced after 'version'
if (headMap.size() == 0) { if (headMap.isEmpty()) {
continue; continue;
} }

View File

@ -132,7 +132,7 @@ public class SettingsExporter {
Set<String> exportAccounts; Set<String> exportAccounts;
if (accountUuids == null) { if (accountUuids == null) {
Account[] accounts = preferences.getAccounts(); List<Account> accounts = preferences.getAccounts();
exportAccounts = new HashSet<String>(); exportAccounts = new HashSet<String>();
for (Account account : accounts) { for (Account account : accounts) {
exportAccounts.add(account.getUuid()); exportAccounts.add(account.getUuid());

View File

@ -340,7 +340,7 @@ public class SettingsImporter {
AccountDescription original = new AccountDescription(account.name, account.uuid); AccountDescription original = new AccountDescription(account.name, account.uuid);
Preferences prefs = Preferences.getPreferences(context); Preferences prefs = Preferences.getPreferences(context);
Account[] accounts = prefs.getAccounts(); List<Account> accounts = prefs.getAccounts();
String uuid = account.uuid; String uuid = account.uuid;
Account existingAccount = prefs.getAccount(uuid); Account existingAccount = prefs.getAccount(uuid);
@ -357,7 +357,7 @@ public class SettingsImporter {
if (isAccountNameUsed(accountName, accounts)) { if (isAccountNameUsed(accountName, accounts)) {
// Account name is already in use. So generate a new one by appending " (x)", where x // Account name is already in use. So generate a new one by appending " (x)", where x
// is the first number >= 1 that results in an unused account name. // is the first number >= 1 that results in an unused account name.
for (int i = 1; i <= accounts.length; i++) { for (int i = 1; i <= accounts.size(); i++) {
accountName = account.name + " (" + i + ")"; accountName = account.name + " (" + i + ")";
if (!isAccountNameUsed(accountName, accounts)) { if (!isAccountNameUsed(accountName, accounts)) {
break; break;
@ -605,7 +605,7 @@ public class SettingsImporter {
} }
} }
private static boolean isAccountNameUsed(String name, Account[] accounts) { private static boolean isAccountNameUsed(String name, List<Account> accounts) {
for (Account account : accounts) { for (Account account : accounts) {
if (account == null) { if (account == null) {
continue; continue;

View File

@ -9,21 +9,24 @@ import android.database.sqlite.SQLiteStatement;
import android.util.Log; import android.util.Log;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import java.net.URI; import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
public class Storage implements SharedPreferences { public class Storage implements SharedPreferences {
private static ConcurrentHashMap<Context, Storage> storages = private static ConcurrentMap<Context, Storage> storages =
new ConcurrentHashMap<Context, Storage>(); new ConcurrentHashMap<Context, Storage>();
private volatile ConcurrentHashMap<String, String> storage = new ConcurrentHashMap<String, String>(); private volatile ConcurrentMap<String, String> storage = new ConcurrentHashMap<String, String>();
private CopyOnWriteArrayList<OnSharedPreferenceChangeListener> listeners = private CopyOnWriteArrayList<OnSharedPreferenceChangeListener> listeners =
new CopyOnWriteArrayList<OnSharedPreferenceChangeListener>(); new CopyOnWriteArrayList<OnSharedPreferenceChangeListener>();
@ -31,11 +34,11 @@ public class Storage implements SharedPreferences {
private int DB_VERSION = 2; private int DB_VERSION = 2;
private String DB_NAME = "preferences_storage"; private String DB_NAME = "preferences_storage";
private ThreadLocal<ConcurrentHashMap<String, String>> workingStorage private ThreadLocal<ConcurrentMap<String, String>> workingStorage
= new ThreadLocal<ConcurrentHashMap<String, String>>(); = new ThreadLocal<ConcurrentMap<String, String>>();
private ThreadLocal<SQLiteDatabase> workingDB = private ThreadLocal<SQLiteDatabase> workingDB =
new ThreadLocal<SQLiteDatabase>(); new ThreadLocal<SQLiteDatabase>();
private ThreadLocal<ArrayList<String>> workingChangedKeys = new ThreadLocal<ArrayList<String>>(); private ThreadLocal<List<String>> workingChangedKeys = new ThreadLocal<List<String>>();
private Context context = null; private Context context = null;
@ -59,11 +62,11 @@ public class Storage implements SharedPreferences {
if (transportUriStr != null) { if (transportUriStr != null) {
String[] userInfoParts = uri.getUserInfo().split(":"); String[] userInfoParts = uri.getUserInfo().split(":");
String usernameEnc = URLEncoder.encode(userInfoParts[0], "UTF-8"); String usernameEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
String passwordEnc = ""; String passwordEnc = "";
String authType = ""; String authType = "";
if (userInfoParts.length > 1) { if (userInfoParts.length > 1) {
passwordEnc = ":" + URLEncoder.encode(userInfoParts[1], "UTF-8"); passwordEnc = ":" + UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
} }
if (userInfoParts.length > 2) { if (userInfoParts.length > 2) {
authType = ":" + userInfoParts[2]; authType = ":" + userInfoParts[2];
@ -83,34 +86,34 @@ public class Storage implements SharedPreferences {
if (storeUriStr.startsWith("imap")) { if (storeUriStr.startsWith("imap")) {
String[] userInfoParts = uri.getUserInfo().split(":"); String[] userInfoParts = uri.getUserInfo().split(":");
if (userInfoParts.length == 2) { if (userInfoParts.length == 2) {
String usernameEnc = URLEncoder.encode(userInfoParts[0], "UTF-8"); String usernameEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
String passwordEnc = URLEncoder.encode(userInfoParts[1], "UTF-8"); String passwordEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
newUserInfo = usernameEnc + ":" + passwordEnc; newUserInfo = usernameEnc + ":" + passwordEnc;
} else { } else {
String authType = userInfoParts[0]; String authType = userInfoParts[0];
String usernameEnc = URLEncoder.encode(userInfoParts[1], "UTF-8"); String usernameEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
String passwordEnc = URLEncoder.encode(userInfoParts[2], "UTF-8"); String passwordEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[2]);
newUserInfo = authType + ":" + usernameEnc + ":" + passwordEnc; newUserInfo = authType + ":" + usernameEnc + ":" + passwordEnc;
} }
} else if (storeUriStr.startsWith("pop3")) { } else if (storeUriStr.startsWith("pop3")) {
String[] userInfoParts = uri.getUserInfo().split(":", 2); String[] userInfoParts = uri.getUserInfo().split(":", 2);
String usernameEnc = URLEncoder.encode(userInfoParts[0], "UTF-8"); String usernameEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
String passwordEnc = ""; String passwordEnc = "";
if (userInfoParts.length > 1) { if (userInfoParts.length > 1) {
passwordEnc = ":" + URLEncoder.encode(userInfoParts[1], "UTF-8"); passwordEnc = ":" + UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
} }
newUserInfo = usernameEnc + passwordEnc; newUserInfo = usernameEnc + passwordEnc;
} else if (storeUriStr.startsWith("webdav")) { } else if (storeUriStr.startsWith("webdav")) {
String[] userInfoParts = uri.getUserInfo().split(":", 2); String[] userInfoParts = uri.getUserInfo().split(":", 2);
String usernameEnc = URLEncoder.encode(userInfoParts[0], "UTF-8"); String usernameEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
String passwordEnc = ""; String passwordEnc = "";
if (userInfoParts.length > 1) { if (userInfoParts.length > 1) {
passwordEnc = ":" + URLEncoder.encode(userInfoParts[1], "UTF-8"); passwordEnc = ":" + UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
} }
newUserInfo = usernameEnc + passwordEnc; newUserInfo = usernameEnc + passwordEnc;
@ -201,7 +204,7 @@ public class Storage implements SharedPreferences {
} }
private void keyChange(String key) { private void keyChange(String key) {
ArrayList<String> changedKeys = workingChangedKeys.get(); List<String> changedKeys = workingChangedKeys.get();
if (!changedKeys.contains(key)) { if (!changedKeys.contains(key)) {
changedKeys.add(key); changedKeys.add(key);
} }
@ -258,14 +261,14 @@ public class Storage implements SharedPreferences {
} }
protected void doInTransaction(Runnable dbWork) { protected void doInTransaction(Runnable dbWork) {
ConcurrentHashMap<String, String> newStorage = new ConcurrentHashMap<String, String>(); ConcurrentMap<String, String> newStorage = new ConcurrentHashMap<String, String>();
newStorage.putAll(storage); newStorage.putAll(storage);
workingStorage.set(newStorage); workingStorage.set(newStorage);
SQLiteDatabase mDb = openDB(); SQLiteDatabase mDb = openDB();
workingDB.set(mDb); workingDB.set(mDb);
ArrayList<String> changedKeys = new ArrayList<String>(); List<String> changedKeys = new ArrayList<String>();
workingChangedKeys.set(changedKeys); workingChangedKeys.set(changedKeys);
mDb.beginTransaction(); mDb.beginTransaction();
@ -287,13 +290,17 @@ public class Storage implements SharedPreferences {
} }
} }
public long size() { public boolean isEmpty() {
return storage.size(); return storage.isEmpty();
} }
//@Override //@Override
public boolean contains(String key) { public boolean contains(String key) {
return storage.contains(key); // TODO this used to be ConcurrentHashMap#contains which is
// actually containsValue. But looking at the usage of this method,
// it's clear that containsKey is what's intended. Investigate if this
// was a bug previously. Looks like it was only used once, when upgrading
return storage.containsKey(key);
} }
//@Override //@Override

View File

@ -15,8 +15,8 @@ import com.fsck.k9.K9;
import com.fsck.k9.Preferences; import com.fsck.k9.Preferences;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.store.LocalStore.AttachmentInfo; import com.fsck.k9.mail.store.local.LocalStore.AttachmentInfo;
import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.mail.store.StorageManager;
import java.io.*; import java.io.*;

View File

@ -11,10 +11,10 @@ import com.fsck.k9.cache.EmailProviderCacheCursor;
import com.fsck.k9.helper.StringUtils; import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LockableDatabase; import com.fsck.k9.mail.store.LockableDatabase;
import com.fsck.k9.mail.store.LockableDatabase.DbCallback; import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.LockableDatabase.WrappedException; import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.store.UnavailableStorageException; import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.search.SqlQueryBuilder; import com.fsck.k9.search.SqlQueryBuilder;

View File

@ -32,7 +32,8 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.search.SearchAccount; import com.fsck.k9.search.SearchAccount;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@ -152,9 +153,9 @@ public class MessageProvider extends ContentProvider {
} }
/** /**
* Extracts the {@link LocalStore.LocalMessage#getId() ID} from the given * Extracts the {@link LocalMessage#getId() ID} from the given
* {@link MessageInfoHolder}. The underlying {@link Message} is expected to * {@link MessageInfoHolder}. The underlying {@link Message} is expected to
* be a {@link LocalStore.LocalMessage}. * be a {@link LocalMessage}.
*/ */
public static class IdExtractor implements FieldExtractor<MessageInfoHolder, Long> { public static class IdExtractor implements FieldExtractor<MessageInfoHolder, Long> {
@Override @Override

View File

@ -256,7 +256,7 @@ public class ConditionsTreeNode implements Parcelable {
* @return List of all nodes in subtree in preorder. * @return List of all nodes in subtree in preorder.
*/ */
public List<ConditionsTreeNode> preorder() { public List<ConditionsTreeNode> preorder() {
ArrayList<ConditionsTreeNode> result = new ArrayList<ConditionsTreeNode>(); List<ConditionsTreeNode> result = new ArrayList<ConditionsTreeNode>();
Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>(); Stack<ConditionsTreeNode> stack = new Stack<ConditionsTreeNode>();
stack.push(this); stack.push(this);

View File

@ -252,7 +252,7 @@ public class LocalSearch implements SearchSpecification {
* real searches because of possible extra conditions to a folder requirement. * real searches because of possible extra conditions to a folder requirement.
*/ */
public List<String> getFolderNames() { public List<String> getFolderNames() {
ArrayList<String> results = new ArrayList<String>(); List<String> results = new ArrayList<String>();
for (ConditionsTreeNode node : mLeafSet) { for (ConditionsTreeNode node : mLeafSet) {
if (node.mCondition.field == Searchfield.FOLDER && if (node.mCondition.field == Searchfield.FOLDER &&
node.mCondition.attribute == Attribute.EQUALS) { node.mCondition.attribute == Attribute.EQUALS) {
@ -328,7 +328,7 @@ public class LocalSearch implements SearchSpecification {
*/ */
@Override @Override
public String[] getAccountUuids() { public String[] getAccountUuids() {
if (mAccountUuids.size() == 0) { if (mAccountUuids.isEmpty()) {
return new String[] { SearchSpecification.ALL_ACCOUNTS }; return new String[] { SearchSpecification.ALL_ACCOUNTS };
} }
@ -343,7 +343,7 @@ public class LocalSearch implements SearchSpecification {
* @return {@code true} if all accounts should be searched. * @return {@code true} if all accounts should be searched.
*/ */
public boolean searchAllAccounts() { public boolean searchAllAccounts() {
return (mAccountUuids.size() == 0); return (mAccountUuids.isEmpty());
} }
/** /**

View File

@ -5,8 +5,8 @@ import java.util.List;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalFolder; import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.search.SearchSpecification.Attribute; import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.SearchCondition; import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.fsck.k9.search.SearchSpecification.Searchfield; import com.fsck.k9.search.SearchSpecification.Searchfield;

View File

@ -1,5 +1,6 @@
package com.fsck.k9.service; package com.fsck.k9.service;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import android.app.Service; import android.app.Service;
@ -186,8 +187,8 @@ public class DatabaseUpgradeService extends Service {
private void upgradeDatabases() { private void upgradeDatabases() {
Preferences preferences = Preferences.getPreferences(this); Preferences preferences = Preferences.getPreferences(this);
Account[] accounts = preferences.getAccounts(); List<Account> accounts = preferences.getAccounts();
mProgressEnd = accounts.length; mProgressEnd = accounts.size();
mProgress = 0; mProgress = 0;
for (Account account : accounts) { for (Account account : accounts) {

View File

@ -1,6 +1,8 @@
package com.fsck.k9.service; package com.fsck.k9.service;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.K9; import com.fsck.k9.K9;
@ -8,6 +10,7 @@ import com.fsck.k9.Preferences;
import com.fsck.k9.activity.MessageCompose; import com.fsck.k9.activity.MessageCompose;
import com.fsck.k9.activity.MessageReference; import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.controller.MessagingController; import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
@ -35,8 +38,7 @@ public class NotificationActionService extends CoreService {
return PendingIntent.getService(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT); return PendingIntent.getService(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT);
} }
public static PendingIntent getReadAllMessagesIntent(Context context, final Account account, public static PendingIntent getReadAllMessagesIntent(Context context, final Account account, final Serializable refs) {
final ArrayList<MessageReference> refs) {
Intent i = new Intent(context, NotificationActionService.class); Intent i = new Intent(context, NotificationActionService.class);
i.putExtra(EXTRA_ACCOUNT, account.getUuid()); i.putExtra(EXTRA_ACCOUNT, account.getUuid());
i.putExtra(EXTRA_MESSAGE_LIST, refs); i.putExtra(EXTRA_MESSAGE_LIST, refs);
@ -53,8 +55,7 @@ public class NotificationActionService extends CoreService {
return PendingIntent.getService(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT); return PendingIntent.getService(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT);
} }
public static Intent getDeleteAllMessagesIntent(Context context, final Account account, public static Intent getDeleteAllMessagesIntent(Context context, final Account account, final Serializable refs) {
final ArrayList<MessageReference> refs) {
Intent i = new Intent(context, NotificationActionService.class); Intent i = new Intent(context, NotificationActionService.class);
i.putExtra(EXTRA_ACCOUNT, account.getUuid()); i.putExtra(EXTRA_ACCOUNT, account.getUuid());
i.putExtra(EXTRA_MESSAGE_LIST, refs); i.putExtra(EXTRA_MESSAGE_LIST, refs);
@ -77,7 +78,7 @@ public class NotificationActionService extends CoreService {
if (K9.DEBUG) if (K9.DEBUG)
Log.i(K9.LOG_TAG, "NotificationActionService marking messages as read"); Log.i(K9.LOG_TAG, "NotificationActionService marking messages as read");
ArrayList<MessageReference> refs = List<MessageReference> refs =
intent.getParcelableArrayListExtra(EXTRA_MESSAGE_LIST); intent.getParcelableArrayListExtra(EXTRA_MESSAGE_LIST);
for (MessageReference ref : refs) { for (MessageReference ref : refs) {
controller.setFlag(account, ref.folderName, ref.uid, Flag.SEEN, true); controller.setFlag(account, ref.folderName, ref.uid, Flag.SEEN, true);
@ -86,9 +87,9 @@ public class NotificationActionService extends CoreService {
if (K9.DEBUG) if (K9.DEBUG)
Log.i(K9.LOG_TAG, "NotificationActionService deleting messages"); Log.i(K9.LOG_TAG, "NotificationActionService deleting messages");
ArrayList<MessageReference> refs = List<MessageReference> refs =
intent.getParcelableArrayListExtra(EXTRA_MESSAGE_LIST); intent.getParcelableArrayListExtra(EXTRA_MESSAGE_LIST);
ArrayList<Message> messages = new ArrayList<Message>(); List<Message> messages = new ArrayList<Message>();
for (MessageReference ref : refs) { for (MessageReference ref : refs) {
Message m = ref.restoreToLocalMessage(this); Message m = ref.restoreToLocalMessage(this);

View File

@ -12,6 +12,7 @@ import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock; import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
public class PollService extends CoreService { public class PollService extends CoreService {
private static String START_SERVICE = "com.fsck.k9.service.PollService.startService"; private static String START_SERVICE = "com.fsck.k9.service.PollService.startService";
@ -77,7 +78,7 @@ public class PollService extends CoreService {
} }
class Listener extends MessagingListener { class Listener extends MessagingListener {
HashMap<String, Integer> accountsChecked = new HashMap<String, Integer>(); Map<String, Integer> accountsChecked = new HashMap<String, Integer>();
private TracingWakeLock wakeLock = null; private TracingWakeLock wakeLock = null;
private int startId = -1; private int startId = -1;

View File

@ -11,6 +11,8 @@ import com.fsck.k9.K9;
import com.fsck.k9.remotecontrol.K9RemoteControl; import com.fsck.k9.remotecontrol.K9RemoteControl;
import com.fsck.k9.Preferences; import com.fsck.k9.Preferences;
import java.util.List;
import static com.fsck.k9.remotecontrol.K9RemoteControl.*; import static com.fsck.k9.remotecontrol.K9RemoteControl.*;
public class RemoteControlReceiver extends CoreReceiver { public class RemoteControlReceiver extends CoreReceiver {
@ -25,12 +27,12 @@ public class RemoteControlReceiver extends CoreReceiver {
} else if (K9RemoteControl.K9_REQUEST_ACCOUNTS.equals(intent.getAction())) { } else if (K9RemoteControl.K9_REQUEST_ACCOUNTS.equals(intent.getAction())) {
try { try {
Preferences preferences = Preferences.getPreferences(context); Preferences preferences = Preferences.getPreferences(context);
Account[] accounts = preferences.getAccounts(); List<Account> accounts = preferences.getAccounts();
String[] uuids = new String[accounts.length]; String[] uuids = new String[accounts.size()];
String[] descriptions = new String[accounts.length]; String[] descriptions = new String[accounts.size()];
for (int i = 0; i < accounts.length; i++) { for (int i = 0; i < accounts.size(); i++) {
//warning: account may not be isAvailable() //warning: account may not be isAvailable()
Account account = accounts[i]; Account account = accounts.get(i);
uuids[i] = account.getUuid(); uuids[i] = account.getUuid();
descriptions[i] = account.getDescription(); descriptions[i] = account.getDescription();

View File

@ -17,6 +17,8 @@ import android.content.SharedPreferences.Editor;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import java.util.List;
public class RemoteControlService extends CoreService { public class RemoteControlService extends CoreService {
private final static String RESCHEDULE_ACTION = "com.fsck.k9.service.RemoteControlService.RESCHEDULE_ACTION"; private final static String RESCHEDULE_ACTION = "com.fsck.k9.service.RemoteControlService.RESCHEDULE_ACTION";
private final static String PUSH_RESTART_ACTION = "com.fsck.k9.service.RemoteControlService.PUSH_RESTART_ACTION"; private final static String PUSH_RESTART_ACTION = "com.fsck.k9.service.RemoteControlService.PUSH_RESTART_ACTION";
@ -65,7 +67,7 @@ public class RemoteControlService extends CoreService {
Log.i(K9.LOG_TAG, "RemoteControlService changing settings for account with UUID " + uuid); Log.i(K9.LOG_TAG, "RemoteControlService changing settings for account with UUID " + uuid);
} }
} }
Account[] accounts = preferences.getAccounts(); List<Account> accounts = preferences.getAccounts();
for (Account account : accounts) { for (Account account : accounts) {
//warning: account may not be isAvailable() //warning: account may not be isAvailable()
if (allAccounts || account.getUuid().equals(uuid)) { if (allAccounts || account.getUuid().equals(uuid)) {

View File

@ -40,7 +40,7 @@ import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part; import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeHeader; import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart; import com.fsck.k9.mail.store.local.LocalAttachmentBodyPart;
import com.fsck.k9.provider.AttachmentProvider; import com.fsck.k9.provider.AttachmentProvider;
public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener { public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener {

View File

@ -5,6 +5,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
@ -279,12 +280,7 @@ public class MessageOpenPgpView extends LinearLayout {
String accName = OpenPgpApiHelper.buildAccountName(identity); String accName = OpenPgpApiHelper.buildAccountName(identity);
intent.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, accName); intent.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, accName);
InputStream is = null; InputStream is = new ByteArrayInputStream(mData.getBytes(Charset.forName("UTF-8")));
try {
is = new ByteArrayInputStream(mData.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
Log.e(K9.LOG_TAG, "UnsupportedEncodingException.", e);
}
final ByteArrayOutputStream os = new ByteArrayOutputStream(); final ByteArrayOutputStream os = new ByteArrayOutputStream();
DecryptVerifyCallback callback = new DecryptVerifyCallback(os, REQUEST_CODE_DECRYPT_VERIFY); DecryptVerifyCallback callback = new DecryptVerifyCallback(os, REQUEST_CODE_DECRYPT_VERIFY);

View File

@ -28,6 +28,7 @@ import android.webkit.WebView;
import android.widget.ScrollView; import android.widget.ScrollView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
/** /**
* A {@link ScrollView} that will never lock scrolling in a particular direction. * A {@link ScrollView} that will never lock scrolling in a particular direction.
@ -59,7 +60,7 @@ public class NonLockingScrollView extends ScrollView {
/** /**
* The list of children who should always receive touch events, and not have them intercepted. * The list of children who should always receive touch events, and not have them intercepted.
*/ */
private final ArrayList<View> mChildrenNeedingAllTouches = new ArrayList<View>(); private final List<View> mChildrenNeedingAllTouches = new ArrayList<View>();
private boolean mSkipWebViewScroll = true; private boolean mSkipWebViewScroll = true;
@ -122,7 +123,7 @@ public class NonLockingScrollView extends ScrollView {
} }
private final Rect sHitFrame = new Rect(); private final Rect sHitFrame = new Rect();
private boolean isEventOverChild(MotionEvent ev, ArrayList<View> children) { private boolean isEventOverChild(MotionEvent ev, List<View> children) {
final int actionIndex = ev.getActionIndex(); final int actionIndex = ev.getActionIndex();
final float x = ev.getX(actionIndex) + getScrollX(); final float x = ev.getX(actionIndex) + getScrollX();
final float y = ev.getY(actionIndex) + getScrollY(); final float y = ev.getY(actionIndex) + getScrollY();

View File

@ -47,6 +47,7 @@ import com.fsck.k9.fragment.MessageViewFragment;
import com.fsck.k9.helper.ClipboardManager; import com.fsck.k9.helper.ClipboardManager;
import com.fsck.k9.helper.Contacts; import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.HtmlConverter; import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Flag;
@ -55,8 +56,8 @@ import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart; import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part; import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.local.LocalAttachmentBodyPart;
import com.fsck.k9.mail.store.LocalStore.LocalMessage; import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns; import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -626,7 +627,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
for (int i = 0; i < mp.getCount(); i++) { for (int i = 0; i < mp.getCount(); i++) {
renderAttachments(mp.getBodyPart(i), depth + 1, message, account, controller, listener); renderAttachments(mp.getBodyPart(i), depth + 1, message, account, controller, listener);
} }
} else if (part instanceof LocalStore.LocalAttachmentBodyPart) { } else if (part instanceof LocalAttachmentBodyPart) {
AttachmentView view = (AttachmentView)mInflater.inflate(R.layout.message_view_attachment, null); AttachmentView view = (AttachmentView)mInflater.inflate(R.layout.message_view_attachment, null);
view.setCallback(attachmentCallback); view.setCallback(attachmentCallback);
@ -790,7 +791,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
// Try to get the filename from the URL // Try to get the filename from the URL
int start = path.lastIndexOf("/"); int start = path.lastIndexOf("/");
if (start != -1 && start + 1 < path.length()) { if (start != -1 && start + 1 < path.length()) {
filename = URLDecoder.decode(path.substring(start + 1), "UTF-8"); filename = UrlEncodingHelper.decodeUtf8(path.substring(start + 1));
} else { } else {
// Use a dummy filename if necessary // Use a dummy filename if necessary
filename = "saved_image"; filename = "saved_image";

0
tests-on-jvm/gradlew vendored Normal file → Executable file
View File

View File

@ -44,7 +44,7 @@ class TestingTextBodyBuilder extends TextBodyBuilder {
// HtmlConverter depends on Android. // HtmlConverter depends on Android.
// So we use dummy method for tests. // So we use dummy method for tests.
@Override @Override
public String textToHtmlFragment(String text) { protected String textToHtmlFragment(String text) {
return "<html>" + text + "</html>"; return "<html>" + text + "</html>";
} }
} }

View File

@ -0,0 +1,236 @@
package com.fsck.k9.mail.internet;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.io.IOUtils;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.internet.MimeMessage;
import android.test.AndroidTestCase;
public class MimeMessageParseTest extends AndroidTestCase {
static {
BinaryTempFileBody.setTempDirectory(new File(System.getProperty("java.io.tmpdir")));
}
private static ByteArrayInputStream toStream(String rawMailData) throws Exception {
return new ByteArrayInputStream(rawMailData.getBytes("ISO-8859-1"));
}
private static MimeMessage parseWithoutRecurse(InputStream data) throws Exception {
return new MimeMessage(data, false);
}
private static MimeMessage parseWithRecurse(InputStream data) throws Exception {
return new MimeMessage(data, true);
}
private static void checkAddresses(Address[] actual, String... expected) {
for (int i = 0; i < actual.length; i++) {
assertEquals(actual[i].toEncodedString(), expected[i]);
}
assertEquals(expected.length, actual.length);
}
private static String streamToString(InputStream stream) throws Exception {
return IOUtils.toString(stream, "ISO-8859-1");
}
private static List<Body> getLeafParts(Body body) {
if (body instanceof Multipart) {
List<Body> ret = new ArrayList<Body>();
for (BodyPart child : ((Multipart) body).getBodyParts()) {
ret.addAll(getLeafParts(child.getBody()));
}
return ret;
} else {
return Collections.singletonList(body);
}
}
private static void checkLeafParts(MimeMessage msg, String... expectedParts) throws Exception {
List<String> actual = new ArrayList<String>();
for (Body leaf : getLeafParts(msg.getBody())) {
actual.add(streamToString(leaf.getInputStream()));
}
assertEquals(Arrays.asList(expectedParts), actual);
}
public static void testSinglePart7BitNoRecurse() throws Exception {
MimeMessage msg = parseWithoutRecurse(toStream(
"From: <adam@example.org>\r\n" +
"To: <eva@example.org>\r\n" +
"Subject: Testmail\r\n" +
"MIME-Version: 1.0\r\n" +
"Content-type: text/plain\r\n" +
"Content-Transfer-Encoding: 7bit\r\n" +
"\r\n" +
"this is some test text."));
checkAddresses(msg.getFrom(), "adam@example.org");
checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org");
assertEquals("Testmail", msg.getSubject());
assertEquals("text/plain", msg.getContentType());
assertEquals("this is some test text.", streamToString(msg.getBody().getInputStream()));
}
public static void testSinglePart8BitRecurse() throws Exception {
MimeMessage msg = parseWithRecurse(toStream(
"From: <adam@example.org>\r\n" +
"To: <eva@example.org>\r\n" +
"Subject: Testmail\r\n" +
"MIME-Version: 1.0\r\n" +
"Content-type: text/plain; encoding=ISO-8859-1\r\n" +
"Content-Transfer-Encoding: 8bit\r\n" +
"\r\n" +
"gefährliche Umlaute"));
checkAddresses(msg.getFrom(), "adam@example.org");
checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org");
assertEquals("Testmail", msg.getSubject());
assertEquals("text/plain; encoding=ISO-8859-1", msg.getContentType());
assertEquals("gefährliche Umlaute", streamToString(msg.getBody().getInputStream()));
}
public static void testSinglePartBase64NoRecurse() throws Exception {
MimeMessage msg = parseWithoutRecurse(toStream(
"From: <adam@example.org>\r\n" +
"To: <eva@example.org>\r\n" +
"Subject: Testmail\r\n" +
"MIME-Version: 1.0\r\n" +
"Content-type: text/plain\r\n" +
"Content-Transfer-Encoding: base64\r\n" +
"\r\n" +
"dGhpcyBpcyBzb21lIG1vcmUgdGVzdCB0ZXh0Lg==\r\n"));
checkAddresses(msg.getFrom(), "adam@example.org");
checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org");
assertEquals("Testmail", msg.getSubject());
assertEquals("text/plain", msg.getContentType());
assertEquals("this is some more test text.", streamToString(msg.getBody().getInputStream()));
}
public static void testMultipartSingleLayerNoRecurse() throws Exception {
MimeMessage msg = parseWithoutRecurse(toStream(
"From: <x@example.org>\r\n" +
"To: <y@example.org>\r\n" +
"Subject: Testmail 2\r\n" +
"MIME-Version: 1.0\n" +
"Content-Type: multipart/mixed; boundary=frontier\n" +
"\n" +
"This is a message with multiple parts in MIME format.\n" +
"--frontier\n" +
"Content-Type: text/plain\n" +
"\n" +
"This is the body of the message.\n" +
"--frontier\n" +
"Content-Type: application/octet-stream\n" +
"Content-Transfer-Encoding: base64\n" +
"\n" +
"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\n" +
"Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg=\n" +
"--frontier--"));
checkAddresses(msg.getFrom(), "x@example.org");
checkAddresses(msg.getRecipients(RecipientType.TO), "y@example.org");
assertEquals("Testmail 2", msg.getSubject());
assertEquals("multipart/mixed; boundary=frontier", msg.getContentType());
checkLeafParts(msg,
"This is the body of the message.",
"<html>\n" +
" <head>\n" +
" </head>\n" +
" <body>\n" +
" <p>This is the body of the message.</p>\n" +
" </body>\n" +
"</html>\n" +
"");
}
public static void testMultipartSingleLayerRecurse() throws Exception {
MimeMessage msg = parseWithRecurse(toStream(
"From: <x@example.org>\r\n" +
"To: <y@example.org>\r\n" +
"Subject: Testmail 2\r\n" +
"MIME-Version: 1.0\n" +
"Content-Type: multipart/mixed; boundary=frontier\n" +
"\n" +
"This is a message with multiple parts in MIME format.\n" +
"--frontier\n" +
"Content-Type: text/plain\n" +
"\n" +
"This is the body of the message.\n" +
"--frontier\n" +
"Content-Type: application/octet-stream\n" +
"Content-Transfer-Encoding: base64\n" +
"\n" +
"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\n" +
"Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg=\n" +
"--frontier--"));
checkAddresses(msg.getFrom(), "x@example.org");
checkAddresses(msg.getRecipients(RecipientType.TO), "y@example.org");
assertEquals("Testmail 2", msg.getSubject());
assertEquals("multipart/mixed; boundary=frontier", msg.getContentType());
checkLeafParts(msg,
"This is the body of the message.",
"<html>\n" +
" <head>\n" +
" </head>\n" +
" <body>\n" +
" <p>This is the body of the message.</p>\n" +
" </body>\n" +
"</html>\n" +
"");
}
public static void testMultipartTwoLayersRecurse() throws Exception {
MimeMessage msg = parseWithRecurse(toStream(
"From: <x@example.org>\r\n" +
"To: <y@example.org>\r\n" +
"Subject: Testmail 2\r\n" +
"MIME-Version: 1.0\n" +
"Content-Type: multipart/mixed; boundary=1\n" +
"\n" +
"This is a message with multiple parts in MIME format.\n" +
"--1\n" +
"Content-Type: text/plain\n" +
"\n" +
"some text in the first part\n" +
"--1\n" +
"Content-Type: multipart/alternative; boundary=2\n" +
"\n" +
"--2\n" +
"Content-Type: text/plain\n" +
"\n" +
"alternative 1\n" +
"--2\n" +
"Content-Type: text/plain\n" +
"\n" +
"alternative 2\n" +
"--2--\n" +
"--1--"));
checkAddresses(msg.getFrom(), "x@example.org");
checkAddresses(msg.getRecipients(RecipientType.TO), "y@example.org");
assertEquals("Testmail 2", msg.getSubject());
assertEquals("multipart/mixed; boundary=1", msg.getContentType());
checkLeafParts(msg,
"some text in the first part",
"alternative 1",
"alternative 2");
}
}

View File

@ -96,69 +96,81 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
+ "Dcf5/G8bZe2DnavBQfML1wI5d7NUWE8CWb95SsIvFXI0qZE0oIR+axBVl9u97uaO\n" + "Dcf5/G8bZe2DnavBQfML1wI5d7NUWE8CWb95SsIvFXI0qZE0oIR+axBVl9u97uaO\n"
+ "-----END CERTIFICATE-----\n"; + "-----END CERTIFICATE-----\n";
private static final String STARFIELD_CERT = private static final String LINUX_COM_FIRST_PARENT_CERT =
"-----BEGIN CERTIFICATE-----\n" + "-----BEGIN CERTIFICATE-----\n" +
"MIIFBzCCA++gAwIBAgICAgEwDQYJKoZIhvcNAQEFBQAwaDELMAkGA1UEBhMCVVMx\n" + "MIIGNDCCBBygAwIBAgIBGzANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW\n" +
"JTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsT\n" + "MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg\n" +
"KVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2\n" + "Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh\n" +
"MTExNjAxMTU0MFoXDTI2MTExNjAxMTU0MFowgdwxCzAJBgNVBAYTAlVTMRAwDgYD\n" + "dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NzA5WhcNMTcxMDI0MjA1NzA5WjCB\n" +
"VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy\n" + "jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT\n" +
"ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTkwNwYDVQQLEzBodHRwOi8vY2VydGlm\n" + "IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0\n" +
"aWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkxMTAvBgNVBAMTKFN0\n" + "YXJ0Q29tIENsYXNzIDIgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB\n" +
"YXJmaWVsZCBTZWN1cmUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxETAPBgNVBAUT\n" + "IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4k85L6GMmoWtCA4IPlfyiAEh\n" +
"CDEwNjg4NDM1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4qddo+1m\n" + "G5SpbOK426oZGEY6UqH1D/RujOqWjJaHeRNAUS8i8gyLhw9l33F0NENVsTUJm9m8\n" +
"72ovKzYf3Y3TBQKgyg9eGa44cs8W2lRKy0gK9KFzEWWFQ8lbFwyaK74PmFF6YCkN\n" + "H/rrQtCXQHK3Q5Y9upadXVACHJuRjZzArNe7LxfXyz6CnXPrB0KSss1ks3RVG7RL\n" +
"bN7i6OUVTVb/kNGnpgQ/YAdKym+lEOez+FyxvCsq3AF59R019Xoog/KTc4KJrGBt\n" + "hiEs93iHMuAW5Nq9TJXqpAp+tgoNLorPVavD5d1Bik7mb2VsskDPF125w2oLJxGE\n" +
"y8JIwh3UBkQXPKwBR6s+cIQJC7ggCEAgh6FjGso+g9I3s5iNMj83v6G3W1/eXDOS\n" + "d2H2wnztwI14FBiZgZl1Y7foU9O6YekO+qIw80aiuckfbIBaQKwn7UhHM7BUxkYa\n" +
"zz4HzrlIS+LwVVAv+HBCidGTlopj2WYN5lhuuW2QvcrchGbyOY5bplhVc8tibBvX\n" + "8zVhwQIpkFR+ZE3EMFICgtffziFuGJHXuKuMJxe18KMBL47SLoc6PbQpZ4rEAwID\n" +
"IBY7LFn1y8hWMkpQJ7pV06gBy3KpdIsMrTrlFbYq32X43or174Q7+edUZQuAvUdF\n" + "AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD\n" +
"pfBE2FM7voDxLwIDAQABo4IBRDCCAUAwHQYDVR0OBBYEFElLUifRG7zyoSFqYntR\n" + "VR0OBBYEFBHbI0X9VMxqcW+EigPXvvcBLyaGMB8GA1UdIwQYMBaAFE4L7xqkQFul\n" +
"QnqK19VWMB8GA1UdIwQYMBaAFL9ft9HO3R+G9FtVrNzXEMIOqYjnMBIGA1UdEwEB\n" + "F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov\n" +
"/wQIMAYBAf8CAQAwOQYIKwYBBQUHAQEELTArMCkGCCsGAQUFBzABhh1odHRwOi8v\n" + "L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0\n" +
"b2NzcC5zdGFyZmllbGR0ZWNoLmNvbTBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8v\n" + "YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3\n" +
"Y2VydGlmaWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9zaXRvcnkvc2Zyb290\n" + "dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0\n" +
"LmNybDBRBgNVHSAESjBIMEYGBFUdIAAwPjA8BggrBgEFBQcCARYwaHR0cDovL2Nl\n" + "c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu\n" +
"cnRpZmljYXRlcy5zdGFyZmllbGR0ZWNoLmNvbS9yZXBvc2l0b3J5MA4GA1UdDwEB\n" + "BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0\n" +
"/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAhlK6sx+mXmuQpmQq/EWyrp8+s2Kv\n" + "BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl\n" +
"2x9nxL3KoS/HnA0hV9D4NiHOOiU+eHaz2d283vtshF8Mow0S6xE7cV+AHvEfbQ5f\n" + "LnBkZjANBgkqhkiG9w0BAQsFAAOCAgEAbQjxXHkqUPtUY+u8NEFcuKMDITfjvGkl\n" +
"wezUpfdlux9MlQETsmqcC+sfnbHn7RkNvIV88xe9WWOupxoFzUfjLZZiUTIKCGhL\n" + "LgrTuBW63grW+2AuDAZRo/066eNHs6QV4i5e4ujwPSR2dgggY7mOIIBmiDm2QRjF\n" +
"Indf90XcYd70yysiKUQl0p8Ld3qhJnxK1w/C0Ty6DqeVmlsFChD5VV/Bl4t0zF4o\n" + "5CROq6zDlIdqlsFZICkuONDNFpFjaPtZRTmuK1n6gywQgCNSIrbzjPcwR/jL/wow\n" +
"aRN+0AqNnQ9gVHrEjBs1D3R6cLKCzx214orbKsayUWm/EheSYBeqPVsJ+IdlHaek\n" + "bfwC9yGme1EeZRqvWy/HzFWacs7UMmWlRk6DTmpfPOPMJo5AxyTZCiCYQQeksV7x\n" +
"KOUiAgOCRJo0Y577KM/ozS4OUiDtSss4fJ2ubnnXlSyokfOGASGRS7VApA==\n" + "UAeY0kWa+y/FV+eerOPUl6yy4jRHTk7tCySxrciZwYbd6YNLmeIQoUAdRC3CH3nT\n" +
"B2/JYxltcgyGHMiPU3TtafZgLs8fvncv+wIF1YAF/OGqg8qmzoJ3ghM4upGdTMIu\n" +
"8vADdmuLC/+dnbzknxX6QEGlWA8zojLUxVhGNfIFoizu/V/DyvSvYuxzzIkPECK5\n" +
"gDoMoBTTMI/wnxXwulNPtfgF7/5AtDhA4GNAfB2SddxiNQAF7XkUHtMZ9ff3W6Xk\n" +
"FldOG+NlLFqsDBG/KLckyFK36gq+FqNFCbmtmtXBGB5L1fDIeYzcMKG6hFQxhHS0\n" +
"oqpdHhp2nWBfLlOnTNqIZNJzOH37OJE6Olk45LNFJtSrqIAZyCCfM6bQgoQvZuIa\n" +
"xs9SIp+63ZMk9TxEaQj/KteaOyfaPXI9778U7JElMTz3Bls62mslV2I1C/A73Zyq\n" +
"JZWQZ8NU4ds=\n" +
"-----END CERTIFICATE-----\n"; "-----END CERTIFICATE-----\n";
private static final String LINUX_COM_CERT = private static final String LINUX_COM_CERT =
"-----BEGIN CERTIFICATE-----\n" + "-----BEGIN CERTIFICATE-----\n" +
"MIIFfDCCBGSgAwIBAgIHJ7DOOMo+MDANBgkqhkiG9w0BAQUFADCB3DELMAkGA1UE\n" + "MIIGhjCCBW6gAwIBAgIDAmiWMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYDVQQGEwJJ\n" +
"BhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAj\n" + "TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0\n" +
"BgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOTA3BgNVBAsTMGh0\n" + "YWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg\n" +
"dHA6Ly9jZXJ0aWZpY2F0ZXMuc3RhcmZpZWxkdGVjaC5jb20vcmVwb3NpdG9yeTEx\n" + "MiBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTQwODIxMjEwMDI4\n" +
"MC8GA1UEAxMoU3RhcmZpZWxkIFNlY3VyZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0\n" + "WhcNMTYwODIxMDY0NDE0WjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlm\n" +
"eTERMA8GA1UEBRMIMTA2ODg0MzUwHhcNMTExMDA1MDI1MTQyWhcNMTQxMDA1MDI1\n" + "b3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xHTAbBgNVBAoTFFRoZSBMaW51\n" +
"MTQyWjBPMRQwEgYDVQQKFAsqLmxpbnV4LmNvbTEhMB8GA1UECxMYRG9tYWluIENv\n" + "eCBGb3VuZGF0aW9uMRQwEgYDVQQDFAsqLmxpbnV4LmNvbTEjMCEGCSqGSIb3DQEJ\n" +
"bnRyb2wgVmFsaWRhdGVkMRQwEgYDVQQDFAsqLmxpbnV4LmNvbTCCASIwDQYJKoZI\n" + "ARYUaG9zdG1hc3RlckBsaW51eC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n" +
"hvcNAQEBBQADggEPADCCAQoCggEBANoZR/TDp2/8LtA8k9Li55I665ssC7rHX+Wk\n" + "ggEKAoIBAQCjvFjOigXyqkSiVv0vz1CSDg08iilLnj8gRFRoRMA6fFWhQTp4QGLV\n" +
"oiGa6xBeCKTvNy9mgaUVzHwrOQlwJ2GbxFI+X0e3W2sWXUDTSxESZSEW2VZnjEn2\n" + "1li5VMEQdZ/vyqTWjmB+FFkuTsBjFDg6gG3yw6DQBGyyM06A1dT9YKUa7LqxOxQr\n" +
"600Qm8XMhZPvqztLRweHH8IuBNNYZHnW4Z2L4DS/Mi03EmjKZt2g3heGQbrv74m4\n" + "KhNOacPS/pAupAZ5jOO7fcZwIcpKcKEjjhHn7GXEVvb+K996TMA0vDYcp1lgXtil\n" +
"v9/g6Jgr5ZOIwES6LUJchSWV2zcL8VYunpxnAtbi2hq1YfA9oYU82ngP40Ds7HEB\n" + "7Ij+1GUSA29NrnCZXUun2c5nS7OulRYcgtRyZBa13zfyaVJtEIl14ClP9gsLa/5u\n" +
"9pUlzcWu9gcasWGzTvbVBZ4nA29pz5zWn1LHYfSYVSmXKU/ggfZb2nXd5/NkbWQX\n" + "eXzZD71Jj48ZNbiKRThiUZ5FkEnljjSQa25Hr5g9DXY2JvI1r8OJOCUR8jPiRyNs\n" +
"7B2SNH9/OVrHtFZldzD1+ddfCt1DQjXfGv7QqpAVsFTdKspPDLMCAwEAAaOCAc0w\n" + "Kgc1ZG0fibm9VoHjokUZ2aQxyQJP/C1TAgMBAAGjggLlMIIC4TAJBgNVHRMEAjAA\n" +
"ggHJMA8GA1UdEwEB/wQFMAMBAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF\n" + "MAsGA1UdDwQEAwIDqDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwHQYD\n" +
"BwMCMA4GA1UdDwEB/wQEAwIFoDA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vY3Js\n" + "VR0OBBYEFI0nMnIXZOz02MlXPh2g9aHesvPPMB8GA1UdIwQYMBaAFBHbI0X9VMxq\n" +
"LnN0YXJmaWVsZHRlY2guY29tL3NmczEtMjAuY3JsMFkGA1UdIARSMFAwTgYLYIZI\n" + "cW+EigPXvvcBLyaGMCEGA1UdEQQaMBiCCyoubGludXguY29tgglsaW51eC5jb20w\n" +
"AYb9bgEHFwEwPzA9BggrBgEFBQcCARYxaHR0cDovL2NlcnRpZmljYXRlcy5zdGFy\n" + "ggFWBgNVHSAEggFNMIIBSTAIBgZngQwBAgIwggE7BgsrBgEEAYG1NwECAzCCASow\n" +
"ZmllbGR0ZWNoLmNvbS9yZXBvc2l0b3J5LzCBjQYIKwYBBQUHAQEEgYAwfjAqBggr\n" + "LgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYw\n" +
"BgEFBQcwAYYeaHR0cDovL29jc3Auc3RhcmZpZWxkdGVjaC5jb20vMFAGCCsGAQUF\n" + "gfcGCCsGAQUFBwICMIHqMCcWIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9y\n" +
"BzAChkRodHRwOi8vY2VydGlmaWNhdGVzLnN0YXJmaWVsZHRlY2guY29tL3JlcG9z\n" + "aXR5MAMCAQEagb5UaGlzIGNlcnRpZmljYXRlIHdhcyBpc3N1ZWQgYWNjb3JkaW5n\n" +
"aXRvcnkvc2ZfaW50ZXJtZWRpYXRlLmNydDAfBgNVHSMEGDAWgBRJS1In0Ru88qEh\n" + "IHRvIHRoZSBDbGFzcyAyIFZhbGlkYXRpb24gcmVxdWlyZW1lbnRzIG9mIHRoZSBT\n" +
"amJ7UUJ6itfVVjAhBgNVHREEGjAYggsqLmxpbnV4LmNvbYIJbGludXguY29tMB0G\n" + "dGFydENvbSBDQSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkgZm9yIHRoZSBpbnRlbmRl\n" +
"A1UdDgQWBBQ44sIiZfPIl4PY51fh2TCZkqtToTANBgkqhkiG9w0BAQUFAAOCAQEA\n" + "ZCBwdXJwb3NlIGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJlbHlpbmcgcGFydHkgb2Js\n" +
"HFMuDtEZ+hIrIp4hnRJXUiTsc4Vaycxd5X/axDzUx+ooT3y2jBw0rcNnFhgD1T3u\n" + "aWdhdGlvbnMuMDUGA1UdHwQuMCwwKqAooCaGJGh0dHA6Ly9jcmwuc3RhcnRzc2wu\n" +
"9zKiOLGXidvy2G/ppy/ymE+gcNqcEzfV1pKggNqStCwpEX1K8GBD46mX5qJ1RxI+\n" + "Y29tL2NydDItY3JsLmNybDCBjgYIKwYBBQUHAQEEgYEwfzA5BggrBgEFBQcwAYYt\n" +
"QoHo/FZe7Vt+dQjHHdGWh27iVWadpBo/FJnHOsTaHewKL8+Aho0M84nxnUolYxzC\n" + "aHR0cDovL29jc3Auc3RhcnRzc2wuY29tL3N1Yi9jbGFzczIvc2VydmVyL2NhMEIG\n" +
"9H3ViEz+mfMISLzvWicxVU71aJ4yI9JmaL1ddRppBovZHOeWshizcMVtFwcza1S0\n" + "CCsGAQUFBzAChjZodHRwOi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9zdWIuY2xh\n" +
"ZfajonXj48ZkXMXGWuomWxE2dGro6ZW6DdyIjTpZHCJuIvGC10J3mHIR5XaTj6mv\n" + "c3MyLnNlcnZlci5jYS5jcnQwIwYDVR0SBBwwGoYYaHR0cDovL3d3dy5zdGFydHNz\n" +
"zkVBz5DhpshQe97x6OGLOA==\n" + "bC5jb20vMA0GCSqGSIb3DQEBCwUAA4IBAQBVkvlwVLfnTNZh1c8j+PQ1t2n6x1dh\n" +
"tQtZiAYWKvZwi+XqLwU8q2zMxKrTDuqyEVyfCtWCiC1Vkpz72pcyXz2dKu7F7ZVL\n" +
"86uVHcc1jAGmL59UCXz8LFbfAMcoVQW1f2WtNwsa/WGnPUes3jFSec+shB+XCpvE\n" +
"WU6mfcZD5TyvbC79Kn5e3Iq+B4DaXhU/BXASRbORgYd8C+dqj++w0PAcMrmjn3D6\n" +
"EmL1ofqpQ8wCJd5C1b5Fr4RbbYpK8v8AASRcp2Qj9WJjyV882FvXOOFj5V2Jjcnh\n" +
"G0h67ElS/klu9rPaZ+vr3iIB56wvk08O2Wq1IND3sN+Ke3UsvuPqDxAv\n" +
"-----END CERTIFICATE-----\n"; "-----END CERTIFICATE-----\n";
private File mKeyStoreFile; private File mKeyStoreFile;
@ -167,7 +179,7 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
private X509Certificate mCert2; private X509Certificate mCert2;
private X509Certificate mCaCert; private X509Certificate mCaCert;
private X509Certificate mCert3; private X509Certificate mCert3;
private X509Certificate mStarfieldCert; private X509Certificate mLinuxComFirstParentCert;
private X509Certificate mLinuxComCert; private X509Certificate mLinuxComCert;
@ -176,7 +188,7 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
mCert2 = loadCert(K9_EXAMPLE_COM_CERT2); mCert2 = loadCert(K9_EXAMPLE_COM_CERT2);
mCaCert = loadCert(CA_CERT); mCaCert = loadCert(CA_CERT);
mCert3 = loadCert(CERT3); mCert3 = loadCert(CERT3);
mStarfieldCert = loadCert(STARFIELD_CERT); mLinuxComFirstParentCert = loadCert(LINUX_COM_FIRST_PARENT_CERT);
mLinuxComCert = loadCert(LINUX_COM_CERT); mLinuxComCert = loadCert(LINUX_COM_CERT);
} }
@ -267,20 +279,20 @@ public class TrustManagerFactoryTest extends AndroidTestCase {
public void testGloballyTrustedCertificateChain() throws Exception { public void testGloballyTrustedCertificateChain() throws Exception {
X509TrustManager trustManager = TrustManagerFactory.get("www.linux.com", PORT1); X509TrustManager trustManager = TrustManagerFactory.get("www.linux.com", PORT1);
X509Certificate[] certificates = new X509Certificate[] { mLinuxComCert, mStarfieldCert }; X509Certificate[] certificates = new X509Certificate[] { mLinuxComCert, mLinuxComFirstParentCert};
trustManager.checkServerTrusted(certificates, "authType"); trustManager.checkServerTrusted(certificates, "authType");
} }
public void testGloballyTrustedCertificateNotMatchingHost() throws Exception { public void testGloballyTrustedCertificateNotMatchingHost() throws Exception {
X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1); X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1);
assertCertificateRejection(trustManager, new X509Certificate[] { mLinuxComCert, mStarfieldCert }); assertCertificateRejection(trustManager, new X509Certificate[] { mLinuxComCert, mLinuxComFirstParentCert});
} }
public void testGloballyTrustedCertificateNotMatchingHostOverride() throws Exception { public void testGloballyTrustedCertificateNotMatchingHostOverride() throws Exception {
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mLinuxComCert); mKeyStore.addCertificate(MATCHING_HOST, PORT1, mLinuxComCert);
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1); X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1);
X509Certificate[] certificates = new X509Certificate[] { mLinuxComCert, mStarfieldCert }; X509Certificate[] certificates = new X509Certificate[] { mLinuxComCert, mLinuxComFirstParentCert};
trustManager.checkServerTrusted(certificates, "authType"); trustManager.checkServerTrusted(certificates, "authType");
} }