diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java
index 54a04197a..e86ee07a1 100644
--- a/src/com/fsck/k9/Account.java
+++ b/src/com/fsck/k9/Account.java
@@ -29,9 +29,12 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Folder.FolderClass;
-import com.fsck.k9.mail.store.StorageManager;
-import com.fsck.k9.mail.store.StorageManager.StorageProvider;
-import com.fsck.k9.mail.store.local.LocalStore;
+import com.fsck.k9.mail.filter.Base64;
+import com.fsck.k9.mail.store.RemoteStore;
+import com.fsck.k9.mail.store.StoreConfig;
+import com.fsck.k9.mailstore.StorageManager;
+import com.fsck.k9.mailstore.StorageManager.StorageProvider;
+import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.StatsColumns;
import com.fsck.k9.search.ConditionsTreeNode;
@@ -40,7 +43,7 @@ import com.fsck.k9.search.SqlQueryBuilder;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.fsck.k9.search.SearchSpecification.Searchfield;
-import com.fsck.k9.security.LocalKeyStore;
+import com.fsck.k9.mail.ssl.LocalKeyStore;
import com.fsck.k9.view.ColorChip;
import com.larswerkman.colorpicker.ColorPicker;
@@ -48,7 +51,7 @@ import com.larswerkman.colorpicker.ColorPicker;
* Account stores all of the settings for a single account defined by the user. It is able to save
* and delete itself given a Preferences to work with. Each account is defined by a UUID.
*/
-public class Account implements BaseAccount {
+public class Account implements BaseAccount, StoreConfig {
/**
* Default value for the inbox folder (never changes for POP3 and IMAP)
*/
@@ -361,9 +364,9 @@ public class Account implements BaseAccount {
SharedPreferences prefs = preferences.getPreferences();
- mStoreUri = Utility.base64Decode(prefs.getString(mUuid + ".storeUri", null));
+ mStoreUri = Base64.decode(prefs.getString(mUuid + ".storeUri", null));
mLocalStorageProviderId = prefs.getString(mUuid + ".localStorageProvider", StorageManager.getInstance(K9.app).getDefaultProviderId());
- mTransportUri = Utility.base64Decode(prefs.getString(mUuid + ".transportUri", null));
+ mTransportUri = Base64.decode(prefs.getString(mUuid + ".transportUri", null));
mDescription = prefs.getString(mUuid + ".description", null);
mAlwaysBcc = prefs.getString(mUuid + ".alwaysBcc", mAlwaysBcc);
mAutomaticCheckIntervalMinutes = prefs.getInt(mUuid + ".automaticCheckIntervalMinutes", -1);
@@ -689,9 +692,9 @@ public class Account implements BaseAccount {
editor.putString("accountUuids", accountUuids);
}
- editor.putString(mUuid + ".storeUri", Utility.base64Encode(mStoreUri));
+ editor.putString(mUuid + ".storeUri", Base64.encode(mStoreUri));
editor.putString(mUuid + ".localStorageProvider", mLocalStorageProviderId);
- editor.putString(mUuid + ".transportUri", Utility.base64Encode(mTransportUri));
+ editor.putString(mUuid + ".transportUri", Base64.encode(mTransportUri));
editor.putString(mUuid + ".description", mDescription);
editor.putString(mUuid + ".alwaysBcc", mAlwaysBcc);
editor.putInt(mUuid + ".automaticCheckIntervalMinutes", mAutomaticCheckIntervalMinutes);
@@ -1076,8 +1079,8 @@ public class Account implements BaseAccount {
return mDraftsFolderName;
}
- public synchronized void setDraftsFolderName(String draftsFolderName) {
- mDraftsFolderName = draftsFolderName;
+ public synchronized void setDraftsFolderName(String name) {
+ mDraftsFolderName = name;
}
/**
@@ -1096,8 +1099,8 @@ public class Account implements BaseAccount {
return K9.ERROR_FOLDER_NAME;
}
- public synchronized void setSentFolderName(String sentFolderName) {
- mSentFolderName = sentFolderName;
+ public synchronized void setSentFolderName(String name) {
+ mSentFolderName = name;
}
/**
@@ -1113,8 +1116,8 @@ public class Account implements BaseAccount {
return mTrashFolderName;
}
- public synchronized void setTrashFolderName(String trashFolderName) {
- mTrashFolderName = trashFolderName;
+ public synchronized void setTrashFolderName(String name) {
+ mTrashFolderName = name;
}
/**
@@ -1145,8 +1148,8 @@ public class Account implements BaseAccount {
return mSpamFolderName;
}
- public synchronized void setSpamFolderName(String spamFolderName) {
- mSpamFolderName = spamFolderName;
+ public synchronized void setSpamFolderName(String name) {
+ mSpamFolderName = name;
}
/**
@@ -1165,8 +1168,8 @@ public class Account implements BaseAccount {
return mAutoExpandFolderName;
}
- public synchronized void setAutoExpandFolderName(String autoExpandFolderName) {
- mAutoExpandFolderName = autoExpandFolderName;
+ public synchronized void setAutoExpandFolderName(String name) {
+ mAutoExpandFolderName = name;
}
public synchronized int getAccountNumber() {
@@ -1289,11 +1292,11 @@ public class Account implements BaseAccount {
}
public LocalStore getLocalStore() throws MessagingException {
- return Store.getLocalInstance(this, K9.app);
+ return LocalStore.getInstance(this, K9.app);
}
public Store getRemoteStore() throws MessagingException {
- return Store.getRemoteInstance(this);
+ return RemoteStore.getInstance(this);
}
// It'd be great if this actually went into the store implementation
@@ -1657,8 +1660,8 @@ public class Account implements BaseAccount {
return mInboxFolderName;
}
- public void setInboxFolderName(String mInboxFolderName) {
- this.mInboxFolderName = mInboxFolderName;
+ public void setInboxFolderName(String name) {
+ this.mInboxFolderName = name;
}
public synchronized boolean syncRemoteDeletions() {
diff --git a/src/com/fsck/k9/EmailAddressValidator.java b/src/com/fsck/k9/EmailAddressValidator.java
index 957a1384b..211d99220 100644
--- a/src/com/fsck/k9/EmailAddressValidator.java
+++ b/src/com/fsck/k9/EmailAddressValidator.java
@@ -4,7 +4,19 @@ package com.fsck.k9;
import android.text.util.Rfc822Tokenizer;
import android.widget.AutoCompleteTextView.Validator;
+import java.util.regex.Pattern;
+
public class EmailAddressValidator implements Validator {
+ private static final Pattern EMAIL_ADDRESS_PATTERN = Pattern.compile(
+ "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
+ "\\@" +
+ "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
+ "(" +
+ "\\." +
+ "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
+ ")+"
+ );
+
public CharSequence fixText(CharSequence invalidText) {
return "";
}
@@ -14,6 +26,6 @@ public class EmailAddressValidator implements Validator {
}
public boolean isValidAddressOnly(CharSequence text) {
- return com.fsck.k9.helper.Regex.EMAIL_ADDRESS_PATTERN.matcher(text).matches();
+ return EMAIL_ADDRESS_PATTERN.matcher(text).matches();
}
}
diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java
index 08c49c5ed..cc359a6ac 100644
--- a/src/com/fsck/k9/K9.java
+++ b/src/com/fsck/k9/K9.java
@@ -36,9 +36,9 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
-import com.fsck.k9.mail.store.local.LocalStore;
+import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.provider.UnreadWidgetProvider;
-import com.fsck.k9.security.LocalKeyStore;
+import com.fsck.k9.mail.ssl.LocalKeyStore;
import com.fsck.k9.service.BootReceiver;
import com.fsck.k9.service.MailService;
import com.fsck.k9.service.ShutdownReceiver;
@@ -136,37 +136,6 @@ public class K9 extends Application {
*/
public static boolean DEBUG = false;
- /**
- * Should K-9 log the conversation it has over the wire with
- * SMTP servers?
- */
-
- public static boolean DEBUG_PROTOCOL_SMTP = true;
-
- /**
- * Should K-9 log the conversation it has over the wire with
- * IMAP servers?
- */
-
- public static boolean DEBUG_PROTOCOL_IMAP = true;
-
-
- /**
- * Should K-9 log the conversation it has over the wire with
- * POP3 servers?
- */
-
- public static boolean DEBUG_PROTOCOL_POP3 = true;
-
- /**
- * Should K-9 log the conversation it has over the wire with
- * WebDAV servers?
- */
-
- public static boolean DEBUG_PROTOCOL_WEBDAV = true;
-
-
-
/**
* If this is enabled than logging that normally hides sensitive information
* like passwords will show that information.
diff --git a/src/com/fsck/k9/Preferences.java b/src/com/fsck/k9/Preferences.java
index 5fd4929a3..03a3cd1be 100644
--- a/src/com/fsck/k9/Preferences.java
+++ b/src/com/fsck/k9/Preferences.java
@@ -13,7 +13,8 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
-import com.fsck.k9.mail.Store;
+import com.fsck.k9.mail.store.RemoteStore;
+import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.preferences.Editor;
import com.fsck.k9.preferences.Storage;
@@ -121,7 +122,12 @@ public class Preferences {
accountsInOrder.remove(account);
}
- Store.removeAccount(account);
+ try {
+ RemoteStore.removeInstance(account);
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "Failed to reset remote store for account " + account.getUuid(), e);
+ }
+ LocalStore.removeAccount(account);
account.deleteCertificates();
account.delete(this);
diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java
index 738ee32a3..0a7041165 100644
--- a/src/com/fsck/k9/activity/Accounts.java
+++ b/src/com/fsck/k9/activity/Accounts.java
@@ -5,7 +5,6 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Calendar;
import java.util.EnumSet;
import java.util.HashSet;
@@ -76,13 +75,12 @@ import com.fsck.k9.activity.setup.Prefs;
import com.fsck.k9.activity.setup.WelcomeMessage;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.SizeFormatter;
-import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ServerSettings;
-import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.internet.MimeUtility;
-import com.fsck.k9.mail.store.StorageManager;
+import com.fsck.k9.mail.store.RemoteStore;
+import com.fsck.k9.mailstore.StorageManager;
import com.fsck.k9.mail.store.WebDavStore;
import com.fsck.k9.preferences.SettingsExporter;
import com.fsck.k9.preferences.SettingsImportExportException;
@@ -771,7 +769,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
}
private void show(final Accounts activity, boolean restore) {
- ServerSettings incoming = Store.decodeStoreUri(mAccount.getStoreUri());
+ ServerSettings incoming = RemoteStore.decodeStoreUri(mAccount.getStoreUri());
ServerSettings outgoing = Transport.decodeTransportUri(mAccount.getTransportUri());
/*
@@ -992,9 +990,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
if (mIncomingPassword != null) {
// Set incoming server password
String storeUri = mAccount.getStoreUri();
- ServerSettings incoming = Store.decodeStoreUri(storeUri);
+ ServerSettings incoming = RemoteStore.decodeStoreUri(storeUri);
ServerSettings newIncoming = incoming.newPassword(mIncomingPassword);
- String newStoreUri = Store.createStoreUri(newIncoming);
+ String newStoreUri = RemoteStore.createStoreUri(newIncoming);
mAccount.setStoreUri(newStoreUri);
}
diff --git a/src/com/fsck/k9/activity/ChooseFolder.java b/src/com/fsck/k9/activity/ChooseFolder.java
index 83e8070b8..91430796d 100644
--- a/src/com/fsck/k9/activity/ChooseFolder.java
+++ b/src/com/fsck/k9/activity/ChooseFolder.java
@@ -280,22 +280,16 @@ public class ChooseFolder extends K9ListActivity {
mAccount.getInboxFolderName().equalsIgnoreCase(name)))) {
continue;
}
- try {
- folder.refresh(prefs);
- Folder.FolderClass fMode = folder.getDisplayClass();
+ Folder.FolderClass fMode = folder.getDisplayClass();
- if ((aMode == Account.FolderMode.FIRST_CLASS &&
- fMode != Folder.FolderClass.FIRST_CLASS) || (
- aMode == Account.FolderMode.FIRST_AND_SECOND_CLASS &&
- fMode != Folder.FolderClass.FIRST_CLASS &&
- fMode != Folder.FolderClass.SECOND_CLASS) || (
- aMode == Account.FolderMode.NOT_SECOND_CLASS &&
- fMode == Folder.FolderClass.SECOND_CLASS)) {
- continue;
- }
- } catch (MessagingException me) {
- Log.e(K9.LOG_TAG, "Couldn't get prefs to check for displayability of folder " +
- folder.getName(), me);
+ if ((aMode == FolderMode.FIRST_CLASS &&
+ fMode != Folder.FolderClass.FIRST_CLASS) || (
+ aMode == FolderMode.FIRST_AND_SECOND_CLASS &&
+ fMode != Folder.FolderClass.FIRST_CLASS &&
+ fMode != Folder.FolderClass.SECOND_CLASS) || (
+ aMode == FolderMode.NOT_SECOND_CLASS &&
+ fMode == Folder.FolderClass.SECOND_CLASS)) {
+ continue;
}
if (folder.isInTopGroup()) {
diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java
index 670419cf1..254b87745 100644
--- a/src/com/fsck/k9/activity/FolderList.java
+++ b/src/com/fsck/k9/activity/FolderList.java
@@ -55,8 +55,7 @@ import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
-import com.fsck.k9.mail.MessagingException;
-import com.fsck.k9.mail.store.local.LocalFolder;
+import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.Searchfield;
@@ -749,20 +748,14 @@ public class FolderList extends K9ListActivity {
Account.FolderMode aMode = account.getFolderDisplayMode();
Preferences prefs = Preferences.getPreferences(getApplication().getApplicationContext());
for (Folder folder : folders) {
- try {
- folder.refresh(prefs);
+ Folder.FolderClass fMode = folder.getDisplayClass();
- Folder.FolderClass fMode = folder.getDisplayClass();
-
- if ((aMode == Account.FolderMode.FIRST_CLASS && fMode != Folder.FolderClass.FIRST_CLASS)
- || (aMode == Account.FolderMode.FIRST_AND_SECOND_CLASS &&
- fMode != Folder.FolderClass.FIRST_CLASS &&
- fMode != Folder.FolderClass.SECOND_CLASS)
- || (aMode == Account.FolderMode.NOT_SECOND_CLASS && fMode == Folder.FolderClass.SECOND_CLASS)) {
- continue;
- }
- } catch (MessagingException me) {
- Log.e(K9.LOG_TAG, "Couldn't get prefs to check for displayability of folder " + folder.getName(), me);
+ if ((aMode == FolderMode.FIRST_CLASS && fMode != Folder.FolderClass.FIRST_CLASS)
+ || (aMode == FolderMode.FIRST_AND_SECOND_CLASS &&
+ fMode != Folder.FolderClass.FIRST_CLASS &&
+ fMode != Folder.FolderClass.SECOND_CLASS)
+ || (aMode == FolderMode.NOT_SECOND_CLASS && fMode == Folder.FolderClass.SECOND_CLASS)) {
+ continue;
}
FolderInfoHolder holder = null;
diff --git a/src/com/fsck/k9/activity/InsertableHtmlContent.java b/src/com/fsck/k9/activity/InsertableHtmlContent.java
index 9821ad784..851f0ca9a 100644
--- a/src/com/fsck/k9/activity/InsertableHtmlContent.java
+++ b/src/com/fsck/k9/activity/InsertableHtmlContent.java
@@ -12,7 +12,7 @@ import java.io.Serializable;
*
* TODO: This container should also have a text part, along with its insertion point. Or maybe a generic InsertableContent and maintain one each for Html and Text?
*/
-public class InsertableHtmlContent implements Serializable {
+class InsertableHtmlContent implements Serializable {
private static final long serialVersionUID = 2397327034L;
// Default to a headerInsertionPoint at the beginning of the message.
private int headerInsertionPoint = 0;
diff --git a/src/com/fsck/k9/activity/MessageCompose.java b/src/com/fsck/k9/activity/MessageCompose.java
index 141399bcc..ff8cbc28d 100644
--- a/src/com/fsck/k9/activity/MessageCompose.java
+++ b/src/com/fsck/k9/activity/MessageCompose.java
@@ -89,6 +89,7 @@ import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.fragment.ProgressDialogFragment;
import com.fsck.k9.helper.ContactItem;
import com.fsck.k9.helper.Contacts;
+import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.IdentityHelper;
import com.fsck.k9.helper.Utility;
@@ -100,16 +101,17 @@ import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
+import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
-import com.fsck.k9.mail.internet.TextBodyBuilder;
-import com.fsck.k9.mail.store.local.LocalAttachmentBody;
-import com.fsck.k9.mail.store.local.TempFileBody;
-import com.fsck.k9.mail.store.local.TempFileMessageBody;
+import com.fsck.k9.mailstore.LocalAttachmentBody;
+import com.fsck.k9.mailstore.LocalMessage;
+import com.fsck.k9.mailstore.TempFileBody;
+import com.fsck.k9.mailstore.TempFileMessageBody;
import com.fsck.k9.view.MessageWebView;
import org.apache.james.mime4j.codec.EncoderUtil;
@@ -435,17 +437,15 @@ public class MessageCompose extends K9Activity implements OnClickListener,
* Get intent for composing a new message as a reply to the given message. If replyAll is true
* the function is reply all instead of simply reply.
* @param context
- * @param account
* @param message
* @param replyAll
* @param messageBody optional, for decrypted messages, null if it should be grabbed from the given message
*/
public static Intent getActionReplyIntent(
- Context context,
- Account account,
- Message message,
- boolean replyAll,
- String messageBody) {
+ Context context,
+ LocalMessage message,
+ boolean replyAll,
+ String messageBody) {
Intent i = new Intent(context, MessageCompose.class);
i.putExtra(EXTRA_MESSAGE_BODY, messageBody);
i.putExtra(EXTRA_MESSAGE_REFERENCE, message.makeMessageReference());
@@ -468,25 +468,22 @@ public class MessageCompose extends K9Activity implements OnClickListener,
*/
public static void actionReply(
Context context,
- Account account,
- Message message,
+ LocalMessage message,
boolean replyAll,
String messageBody) {
- context.startActivity(getActionReplyIntent(context, account, message, replyAll, messageBody));
+ context.startActivity(getActionReplyIntent(context, message, replyAll, messageBody));
}
/**
* Compose a new message as a forward of the given message.
* @param context
- * @param account
* @param message
* @param messageBody optional, for decrypted messages, null if it should be grabbed from the given message
*/
public static void actionForward(
- Context context,
- Account account,
- Message message,
- String messageBody) {
+ Context context,
+ LocalMessage message,
+ String messageBody) {
Intent i = new Intent(context, MessageCompose.class);
i.putExtra(EXTRA_MESSAGE_BODY, messageBody);
i.putExtra(EXTRA_MESSAGE_REFERENCE, message.makeMessageReference());
@@ -1339,7 +1336,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
*/
private MimeMessage createMessage(boolean isDraft) throws MessagingException {
MimeMessage message = new MimeMessage();
- message.addSentDate(new Date());
+ message.addSentDate(new Date(), K9.hideTimeZone());
Address from = new Address(mIdentity.getEmail(), mIdentity.getName());
message.setFrom(from);
message.setRecipients(RecipientType.TO, getAddresses(mToView));
@@ -1658,7 +1655,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
// First item is the body length. We use this to separate the composed reply from the quoted text.
if (tokenizer.hasMoreTokens()) {
- String bodyLengthS = Utility.base64Decode(tokenizer.nextToken());
+ String bodyLengthS = Base64.decode(tokenizer.nextToken());
try {
identity.put(IdentityField.LENGTH, Integer.valueOf(bodyLengthS).toString());
} catch (Exception e) {
@@ -1666,16 +1663,16 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
}
if (tokenizer.hasMoreTokens()) {
- identity.put(IdentityField.SIGNATURE, Utility.base64Decode(tokenizer.nextToken()));
+ identity.put(IdentityField.SIGNATURE, Base64.decode(tokenizer.nextToken()));
}
if (tokenizer.hasMoreTokens()) {
- identity.put(IdentityField.NAME, Utility.base64Decode(tokenizer.nextToken()));
+ identity.put(IdentityField.NAME, Base64.decode(tokenizer.nextToken()));
}
if (tokenizer.hasMoreTokens()) {
- identity.put(IdentityField.EMAIL, Utility.base64Decode(tokenizer.nextToken()));
+ identity.put(IdentityField.EMAIL, Base64.decode(tokenizer.nextToken()));
}
if (tokenizer.hasMoreTokens()) {
- identity.put(IdentityField.QUOTED_TEXT_MODE, Utility.base64Decode(tokenizer.nextToken()));
+ identity.put(IdentityField.QUOTED_TEXT_MODE, Base64.decode(tokenizer.nextToken()));
}
}
@@ -2646,7 +2643,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
* @param message
* The source message used to populate the various text fields.
*/
- private void processSourceMessage(Message message) {
+ private void processSourceMessage(LocalMessage message) {
try {
switch (mAction) {
case REPLY:
@@ -2800,7 +2797,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
}
- private void processDraftMessage(Message message) throws MessagingException {
+ private void processDraftMessage(LocalMessage message) throws MessagingException {
String showQuotedTextMode = "NONE";
mDraftId = MessagingController.getInstance(getApplication()).getId(message);
@@ -2852,7 +2849,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
newIdentity.setSignature(k9identity.get(IdentityField.SIGNATURE));
mSignatureChanged = true;
} else {
- newIdentity.setSignatureUse(message.getFolder().getAccount().getSignatureUse());
+ newIdentity.setSignatureUse(message.getFolder().getSignatureUse());
newIdentity.setSignature(mIdentity.getSignature());
}
@@ -2962,7 +2959,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
Part part = MimeUtility.findFirstPartByMimeType(message, "text/html");
if (part != null) { // Shouldn't happen if we were the one who saved it.
mQuotedTextFormat = SimpleMessageFormat.HTML;
- String text = MimeUtility.getTextFromPart(part);
+ String text = MessageExtractor.getTextFromPart(part);
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
@@ -3027,7 +3024,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
boolean viewMessageContent) throws MessagingException {
Part textPart = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (textPart != null) {
- String text = MimeUtility.getTextFromPart(textPart);
+ String text = MessageExtractor.getTextFromPart(textPart);
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
@@ -3230,7 +3227,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, HTML found.");
}
- return MimeUtility.getTextFromPart(part);
+ return MessageExtractor.getTextFromPart(part);
}
part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
@@ -3238,7 +3235,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, text found.");
}
- return HtmlConverter.textToHtml(MimeUtility.getTextFromPart(part));
+ String text = MessageExtractor.getTextFromPart(part);
+ return HtmlConverter.textToHtml(text);
}
} else if (format == SimpleMessageFormat.TEXT) {
// Text takes precedence, then html.
@@ -3247,7 +3245,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, text found.");
}
- return MimeUtility.getTextFromPart(part);
+ return MessageExtractor.getTextFromPart(part);
}
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
@@ -3255,7 +3253,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, HTML found.");
}
- return HtmlConverter.htmlToText(MimeUtility.getTextFromPart(part));
+ String text = MessageExtractor.getTextFromPart(part);
+ return HtmlConverter.htmlToText(text);
}
}
@@ -3437,7 +3436,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
updateMessageFormat();
} else {
- processSourceMessage(message);
+ processSourceMessage((LocalMessage) message);
mSourceProcessed = true;
}
}
diff --git a/src/com/fsck/k9/activity/MessageInfoHolder.java b/src/com/fsck/k9/activity/MessageInfoHolder.java
index eecf4f80d..09708fbe6 100644
--- a/src/com/fsck/k9/activity/MessageInfoHolder.java
+++ b/src/com/fsck/k9/activity/MessageInfoHolder.java
@@ -1,7 +1,8 @@
package com.fsck.k9.activity;
import java.util.Date;
-import com.fsck.k9.mail.Message;
+
+import com.fsck.k9.mailstore.LocalMessage;
public class MessageInfoHolder {
public String date;
@@ -18,7 +19,7 @@ public class MessageInfoHolder {
public boolean forwarded;
public boolean flagged;
public boolean dirty;
- public Message message;
+ public LocalMessage message;
public FolderInfoHolder folder;
public boolean selected;
public String account;
@@ -31,7 +32,7 @@ public class MessageInfoHolder {
@Override
public boolean equals(Object o) {
- if (o instanceof MessageInfoHolder == false) {
+ if (!(o instanceof MessageInfoHolder)) {
return false;
}
MessageInfoHolder other = (MessageInfoHolder)o;
diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java
index 7f079db3e..68f2c12ef 100644
--- a/src/com/fsck/k9/activity/MessageList.java
+++ b/src/com/fsck/k9/activity/MessageList.java
@@ -42,8 +42,8 @@ import com.fsck.k9.fragment.MessageListFragment;
import com.fsck.k9.fragment.MessageListFragment.MessageListFragmentListener;
import com.fsck.k9.fragment.MessageViewFragment;
import com.fsck.k9.fragment.MessageViewFragment.MessageViewFragmentListener;
-import com.fsck.k9.mail.Message;
-import com.fsck.k9.mail.store.StorageManager;
+import com.fsck.k9.mailstore.StorageManager;
+import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchAccount;
import com.fsck.k9.search.SearchSpecification;
@@ -1193,23 +1193,23 @@ public class MessageList extends K9Activity implements MessageListFragmentListen
}
@Override
- public void onResendMessage(Message message) {
+ public void onResendMessage(LocalMessage message) {
MessageCompose.actionEditDraft(this, message.makeMessageReference());
}
@Override
- public void onForward(Message message) {
- MessageCompose.actionForward(this, message.getFolder().getAccount(), message, null);
+ public void onForward(LocalMessage message) {
+ MessageCompose.actionForward(this, message, null);
}
@Override
- public void onReply(Message message) {
- MessageCompose.actionReply(this, message.getFolder().getAccount(), message, false, null);
+ public void onReply(LocalMessage message) {
+ MessageCompose.actionReply(this, message, false, null);
}
@Override
- public void onReplyAll(Message message) {
- MessageCompose.actionReply(this, message.getFolder().getAccount(), message, true, null);
+ public void onReplyAll(LocalMessage message) {
+ MessageCompose.actionReply(this, message, true, null);
}
@Override
@@ -1399,18 +1399,18 @@ public class MessageList extends K9Activity implements MessageListFragmentListen
}
@Override
- public void onReply(Message message, PgpData pgpData) {
- MessageCompose.actionReply(this, mAccount, message, false, pgpData.getDecryptedData());
+ public void onReply(LocalMessage message, PgpData pgpData) {
+ MessageCompose.actionReply(this, message, false, pgpData.getDecryptedData());
}
@Override
- public void onReplyAll(Message message, PgpData pgpData) {
- MessageCompose.actionReply(this, mAccount, message, true, pgpData.getDecryptedData());
+ public void onReplyAll(LocalMessage message, PgpData pgpData) {
+ MessageCompose.actionReply(this, message, true, pgpData.getDecryptedData());
}
@Override
- public void onForward(Message mMessage, PgpData mPgpData) {
- MessageCompose.actionForward(this, mAccount, mMessage, mPgpData.getDecryptedData());
+ public void onForward(LocalMessage mMessage, PgpData mPgpData) {
+ MessageCompose.actionForward(this, mMessage, mPgpData.getDecryptedData());
}
@Override
diff --git a/src/com/fsck/k9/activity/MessageReference.java b/src/com/fsck/k9/activity/MessageReference.java
index 990d99774..131b0715a 100644
--- a/src/com/fsck/k9/activity/MessageReference.java
+++ b/src/com/fsck/k9/activity/MessageReference.java
@@ -8,11 +8,11 @@ import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
-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.MessagingException;
+import com.fsck.k9.mailstore.LocalFolder;
+import com.fsck.k9.mailstore.LocalMessage;
+import com.fsck.k9.mail.filter.Base64;
import java.util.StringTokenizer;
@@ -49,9 +49,9 @@ public class MessageReference implements Parcelable {
// Split the identity, stripping away the first two characters representing the version and delimiter.
StringTokenizer tokens = new StringTokenizer(identity.substring(2), IDENTITY_SEPARATOR, false);
if (tokens.countTokens() >= 3) {
- accountUuid = Utility.base64Decode(tokens.nextToken());
- folderName = Utility.base64Decode(tokens.nextToken());
- uid = Utility.base64Decode(tokens.nextToken());
+ accountUuid = Base64.decode(tokens.nextToken());
+ folderName = Base64.decode(tokens.nextToken());
+ uid = Base64.decode(tokens.nextToken());
if (tokens.hasMoreTokens()) {
final String flagString = tokens.nextToken();
@@ -80,11 +80,11 @@ public class MessageReference implements Parcelable {
refString.append(IDENTITY_VERSION_1);
refString.append(IDENTITY_SEPARATOR);
- refString.append(Utility.base64Encode(accountUuid));
+ refString.append(Base64.encode(accountUuid));
refString.append(IDENTITY_SEPARATOR);
- refString.append(Utility.base64Encode(folderName));
+ refString.append(Base64.encode(folderName));
refString.append(IDENTITY_SEPARATOR);
- refString.append(Utility.base64Encode(uid));
+ refString.append(Base64.encode(uid));
if (flag != null) {
refString.append(IDENTITY_SEPARATOR);
refString.append(flag.name());
@@ -128,13 +128,13 @@ public class MessageReference implements Parcelable {
'}';
}
- public Message restoreToLocalMessage(Context context) {
+ public LocalMessage restoreToLocalMessage(Context context) {
try {
Account account = Preferences.getPreferences(context).getAccount(accountUuid);
if (account != null) {
- Folder folder = account.getLocalStore().getFolder(folderName);
+ LocalFolder folder = account.getLocalStore().getFolder(folderName);
if (folder != null) {
- Message message = folder.getMessage(uid);
+ LocalMessage message = folder.getMessage(uid);
if (message != null) {
return message;
} else {
diff --git a/src/com/fsck/k9/mail/internet/TextBodyBuilder.java b/src/com/fsck/k9/activity/TextBodyBuilder.java
similarity index 97%
rename from src/com/fsck/k9/mail/internet/TextBodyBuilder.java
rename to src/com/fsck/k9/activity/TextBodyBuilder.java
index 7efbc8f5d..4dc0a0666 100644
--- a/src/com/fsck/k9/mail/internet/TextBodyBuilder.java
+++ b/src/com/fsck/k9/activity/TextBodyBuilder.java
@@ -1,14 +1,14 @@
-package com.fsck.k9.mail.internet;
+package com.fsck.k9.activity;
import android.text.TextUtils;
import android.util.Log;
import com.fsck.k9.K9;
-import com.fsck.k9.activity.InsertableHtmlContent;
-import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.mail.Body;
+import com.fsck.k9.helper.HtmlConverter;
+import com.fsck.k9.mail.internet.TextBody;
-public class TextBodyBuilder {
+class TextBodyBuilder {
private boolean mIncludeQuotedText = true;
private boolean mReplyAfterQuote = false;
private boolean mSignatureBeforeQuotedText = false;
@@ -27,7 +27,7 @@ public class TextBodyBuilder {
/**
* Build the {@link Body} that will contain the text of the message.
*
- * @return {@link TextBody} instance that contains the entered text and
+ * @return {@link com.fsck.k9.mail.internet.TextBody} instance that contains the entered text and
* possibly the quoted original message.
*/
public TextBody buildTextHtml() {
diff --git a/src/com/fsck/k9/activity/UpgradeDatabases.java b/src/com/fsck/k9/activity/UpgradeDatabases.java
index b0cf3587a..ae8cd6fd7 100644
--- a/src/com/fsck/k9/activity/UpgradeDatabases.java
+++ b/src/com/fsck/k9/activity/UpgradeDatabases.java
@@ -30,7 +30,7 @@ import android.widget.TextView;
*
{@link #actionUpgradeDatabases(Context, Intent)} will call {@link K9#areDatabasesUpToDate()}
* to check if we already know whether the databases have been upgraded.
* {@link K9#areDatabasesUpToDate()} will compare the last known database version stored in a
- * {@link SharedPreferences} file to {@link com.fsck.k9.mail.store.local.LocalStore#DB_VERSION}. This
+ * {@link SharedPreferences} file to {@link com.fsck.k9.mailstore.LocalStore#DB_VERSION}. This
* is done as an optimization because it's faster than opening all of the accounts' databases
* one by one.
* If there was an error reading the cached database version or if it shows the databases need
diff --git a/src/com/fsck/k9/activity/setup/AccountSettings.java b/src/com/fsck/k9/activity/setup/AccountSettings.java
index ba4a82658..c6ee715de 100644
--- a/src/com/fsck/k9/activity/setup/AccountSettings.java
+++ b/src/com/fsck/k9/activity/setup/AccountSettings.java
@@ -36,8 +36,8 @@ import com.fsck.k9.activity.K9PreferenceActivity;
import com.fsck.k9.activity.ManageIdentities;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Store;
-import com.fsck.k9.mail.store.local.LocalFolder;
-import com.fsck.k9.mail.store.StorageManager;
+import com.fsck.k9.mailstore.LocalFolder;
+import com.fsck.k9.mailstore.StorageManager;
import com.fsck.k9.service.MailService;
import org.openintents.openpgp.util.OpenPgpListPreference;
diff --git a/src/com/fsck/k9/activity/setup/AccountSetupBasics.java b/src/com/fsck/k9/activity/setup/AccountSetupBasics.java
index 56d409743..eab3db5fd 100644
--- a/src/com/fsck/k9/activity/setup/AccountSetupBasics.java
+++ b/src/com/fsck/k9/activity/setup/AccountSetupBasics.java
@@ -38,9 +38,9 @@ import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.ServerSettings;
-import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.store.ImapStore;
+import com.fsck.k9.mail.store.RemoteStore;
import com.fsck.k9.mail.transport.SmtpTransport;
import com.fsck.k9.view.ClientCertificateSpinner;
import com.fsck.k9.view.ClientCertificateSpinner.OnClientCertificateChangedListener;
@@ -421,7 +421,7 @@ public class AccountSetupBasics extends K9Activity
ConnectionSecurity.SSL_TLS_REQUIRED, authenticationType, user, password, clientCertificateAlias);
ServerSettings transportServer = new ServerSettings(SmtpTransport.TRANSPORT_TYPE, "mail." + domain, -1,
ConnectionSecurity.SSL_TLS_REQUIRED, authenticationType, user, password, clientCertificateAlias);
- String storeUri = Store.createStoreUri(storeServer);
+ String storeUri = RemoteStore.createStoreUri(storeServer);
String transportUri = Transport.createTransportUri(transportServer);
mAccount.setStoreUri(storeUri);
mAccount.setTransportUri(transportUri);
diff --git a/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java b/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java
index ff4cd531b..5d417309f 100644
--- a/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java
+++ b/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java
@@ -27,6 +27,7 @@ import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.store.ImapStore;
import com.fsck.k9.mail.store.Pop3Store;
+import com.fsck.k9.mail.store.RemoteStore;
import com.fsck.k9.mail.store.WebDavStore;
import com.fsck.k9.mail.store.ImapStore.ImapStoreSettings;
import com.fsck.k9.mail.store.WebDavStore.WebDavStoreSettings;
@@ -163,7 +164,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
}
try {
- ServerSettings settings = Store.decodeStoreUri(mAccount.getStoreUri());
+ ServerSettings settings = RemoteStore.decodeStoreUri(mAccount.getStoreUri());
if (savedInstanceState == null) {
// The first item is selected if settings.authenticationType is null or is not in mAuthTypeAdapter
@@ -610,7 +611,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
ServerSettings settings = new ServerSettings(mStoreType, host, port,
connectionSecurity, authType, username, password, clientCertificateAlias, extra);
- mAccount.setStoreUri(Store.createStoreUri(settings));
+ mAccount.setStoreUri(RemoteStore.createStoreUri(settings));
mAccount.setCompression(Account.TYPE_MOBILE, mCompressionMobile.isChecked());
mAccount.setCompression(Account.TYPE_WIFI, mCompressionWifi.isChecked());
diff --git a/src/com/fsck/k9/activity/setup/FolderSettings.java b/src/com/fsck/k9/activity/setup/FolderSettings.java
index 31bb8e95c..737e2ffc3 100644
--- a/src/com/fsck/k9/activity/setup/FolderSettings.java
+++ b/src/com/fsck/k9/activity/setup/FolderSettings.java
@@ -16,8 +16,8 @@ import com.fsck.k9.mail.Folder.FolderClass;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
-import com.fsck.k9.mail.store.local.LocalFolder;
-import com.fsck.k9.mail.store.local.LocalStore;
+import com.fsck.k9.mailstore.LocalFolder;
+import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.service.MailService;
public class FolderSettings extends K9PreferenceActivity {
diff --git a/src/com/fsck/k9/cache/EmailProviderCache.java b/src/com/fsck/k9/cache/EmailProviderCache.java
index 9b3e7d5e0..81b785997 100644
--- a/src/com/fsck/k9/cache/EmailProviderCache.java
+++ b/src/com/fsck/k9/cache/EmailProviderCache.java
@@ -11,8 +11,8 @@ import android.support.v4.content.LocalBroadcastManager;
import com.fsck.k9.fragment.MessageListFragment;
import com.fsck.k9.mail.Message;
-import com.fsck.k9.mail.store.local.LocalFolder;
-import com.fsck.k9.mail.store.local.LocalMessage;
+import com.fsck.k9.mailstore.LocalFolder;
+import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.provider.EmailProvider;
/**
@@ -123,13 +123,11 @@ public class EmailProviderCache {
}
}
- public void hideMessages(List messages) {
+ public void hideMessages(List messages) {
synchronized (mHiddenMessageCache) {
- for (Message message : messages) {
- LocalMessage localMessage = (LocalMessage) message;
- long messageId = localMessage.getId();
- long folderId = ((LocalFolder) localMessage.getFolder()).getId();
- mHiddenMessageCache.put(messageId, folderId);
+ for (LocalMessage message : messages) {
+ long messageId = message.getId();
+ mHiddenMessageCache.put(messageId, message.getFolder().getId());
}
}
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index 7f889650b..770742256 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -61,6 +61,7 @@ import com.fsck.k9.activity.setup.AccountSetupIncoming;
import com.fsck.k9.activity.setup.AccountSetupOutgoing;
import com.fsck.k9.cache.EmailProviderCache;
import com.fsck.k9.helper.Contacts;
+import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
import com.fsck.k9.mail.Address;
@@ -78,16 +79,18 @@ import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.Pusher;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
+import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
-import com.fsck.k9.mail.store.local.LocalFolder;
-import com.fsck.k9.mail.store.local.LocalMessage;
-import com.fsck.k9.mail.store.local.LocalStore;
-import com.fsck.k9.mail.store.local.LocalStore.PendingCommand;
+import com.fsck.k9.mailstore.MessageRemovalListener;
+import com.fsck.k9.mail.MessageRetrievalListener;
+import com.fsck.k9.mailstore.LocalFolder;
+import com.fsck.k9.mailstore.LocalMessage;
+import com.fsck.k9.mailstore.LocalStore;
+import com.fsck.k9.mailstore.LocalStore.PendingCommand;
import com.fsck.k9.mail.store.Pop3Store;
-import com.fsck.k9.mail.store.UnavailableAccountException;
-import com.fsck.k9.mail.store.UnavailableStorageException;
+import com.fsck.k9.mailstore.UnavailableStorageException;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.StatsColumns;
import com.fsck.k9.search.ConditionsTreeNode;
@@ -212,7 +215,7 @@ public class MessagingController implements Runnable {
* Don't modify this list directly, but use {@link addMessage} and
* {@link removeMatchingMessage} instead.
*/
- LinkedList messages;
+ LinkedList messages;
/**
* List of references for messages that the user is still to be notified of,
* but which don't fit into the inbox style anymore. It's sorted from newest
@@ -236,7 +239,7 @@ public class MessagingController implements Runnable {
public NotificationData(int unread) {
unreadBeforeNotification = unread;
droppedMessages = new LinkedList();
- messages = new LinkedList();
+ messages = new LinkedList();
}
/**
@@ -247,9 +250,9 @@ public class MessagingController implements Runnable {
*
* @param m The new message to add.
*/
- public void addMessage(Message m) {
+ public void addMessage(LocalMessage m) {
while (messages.size() >= MAX_MESSAGES) {
- Message dropped = messages.removeLast();
+ LocalMessage dropped = messages.removeLast();
droppedMessages.addFirst(dropped.makeMessageReference());
}
messages.addFirst(m);
@@ -270,10 +273,10 @@ public class MessagingController implements Runnable {
}
}
- for (Message message : messages) {
+ for (LocalMessage message : messages) {
if (message.makeMessageReference().equals(ref)) {
if (messages.remove(message) && !droppedMessages.isEmpty()) {
- Message restoredMessage = droppedMessages.getFirst().restoreToLocalMessage(context);
+ LocalMessage restoredMessage = droppedMessages.getFirst().restoreToLocalMessage(context);
if (restoredMessage != null) {
messages.addLast(restoredMessage);
droppedMessages.removeFirst();
@@ -291,7 +294,7 @@ public class MessagingController implements Runnable {
* List.
*/
public void supplyAllMessageRefs(List refs) {
- for (Message m : messages) {
+ for (LocalMessage m : messages) {
refs.add(m.makeMessageReference());
}
refs.addAll(droppedMessages);
@@ -312,7 +315,7 @@ public class MessagingController implements Runnable {
private static final Set SYNC_FLAGS = EnumSet.of(Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED);
- private void suppressMessages(Account account, List messages) {
+ private void suppressMessages(Account account, List messages) {
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
mApplication.getApplicationContext());
cache.hideMessages(messages);
@@ -324,13 +327,11 @@ public class MessagingController implements Runnable {
cache.unhideMessages(messages);
}
- private boolean isMessageSuppressed(Account account, Message message) {
- LocalMessage localMessage = (LocalMessage) message;
- String accountUuid = account.getUuid();
- long messageId = localMessage.getId();
- long folderId = ((LocalFolder) localMessage.getFolder()).getId();
+ private boolean isMessageSuppressed(LocalMessage message) {
+ long messageId = message.getId();
+ long folderId = message.getFolder().getId();
- EmailProviderCache cache = EmailProviderCache.getCache(accountUuid,
+ EmailProviderCache cache = EmailProviderCache.getCache(message.getFolder().getAccountUuid(),
mApplication.getApplicationContext());
return cache.isMessageHidden(messageId, folderId);
}
@@ -690,15 +691,16 @@ public class MessagingController implements Runnable {
}
// Collecting statistics of the search result
- MessageRetrievalListener retrievalListener = new MessageRetrievalListener() {
+ MessageRetrievalListener retrievalListener = new MessageRetrievalListener() {
@Override
public void messageStarted(String message, int number, int ofTotal) {}
@Override
public void messagesFinished(int number) {}
@Override
- public void messageFinished(Message message, int number, int ofTotal) {
- if (!isMessageSuppressed(message.getFolder().getAccount(), message)) {
- List messages = new ArrayList();
+
+ public void messageFinished(LocalMessage message, int number, int ofTotal) {
+ if (!isMessageSuppressed(message)) {
+ List messages = new ArrayList();
messages.add(message);
stats.unreadMessageCount += (!message.isSet(Flag.SEEN)) ? 1 : 0;
@@ -762,7 +764,7 @@ public class MessagingController implements Runnable {
final Account acct = Preferences.getPreferences(mApplication.getApplicationContext()).getAccount(acctUuid);
if (listener != null) {
- listener.remoteSearchStarted(acct, folderName);
+ listener.remoteSearchStarted(folderName);
}
List extraResults = new ArrayList();
@@ -791,7 +793,7 @@ public class MessagingController implements Runnable {
messages.clear();
if (listener != null) {
- listener.remoteSearchServerQueryComplete(acct, folderName, remoteMessages.size());
+ listener.remoteSearchServerQueryComplete(folderName, remoteMessages.size(), acct.getRemoteSearchNumResults());
}
Collections.sort(remoteMessages, new UidReverseComparator());
@@ -811,13 +813,13 @@ public class MessagingController implements Runnable {
} else {
Log.e(K9.LOG_TAG, "Could not complete remote search", e);
if (listener != null) {
- listener.remoteSearchFailed(acct, null, e.getMessage());
+ listener.remoteSearchFailed(null, e.getMessage());
}
addErrorMessage(acct, null, e);
}
} finally {
if (listener != null) {
- listener.remoteSearchFinished(acct, folderName, 0, extraResults);
+ listener.remoteSearchFinished(folderName, 0, acct.getRemoteSearchNumResults(), extraResults);
}
}
@@ -878,7 +880,7 @@ public class MessagingController implements Runnable {
}
if (listener != null) {
- listener.remoteSearchAddMessage(remoteFolder.getAccount(), remoteFolder.getName(), localMsg, i, messages.size());
+ listener.remoteSearchAddMessage(remoteFolder.getName(), localMsg, i, messages.size());
}
}
}
@@ -1455,7 +1457,7 @@ 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,
List unsyncedMessages,
final List smallMessages,
@@ -1473,9 +1475,9 @@ public class MessagingController implements Runnable {
final List chunk = new ArrayList(UNSYNC_CHUNK_SIZE);
remoteFolder.fetch(unsyncedMessages, fp,
- new MessageRetrievalListener() {
+ new MessageRetrievalListener() {
@Override
- public void messageFinished(Message message, int number, int ofTotal) {
+ public void messageFinished(T message, int number, int ofTotal) {
try {
String newPushState = remoteFolder.getNewPushState(localFolder.getPushState(), message);
if (newPushState != null) {
@@ -1564,7 +1566,7 @@ public class MessagingController implements Runnable {
localFolder.appendMessages(messages);
for (final Message message : messages) {
- final Message localMessage = localFolder.getMessage(message.getUid());
+ final LocalMessage localMessage = localFolder.getMessage(message.getUid());
syncFlags(localMessage, message);
if (K9.DEBUG)
Log.v(K9.LOG_TAG, "About to notify listeners that we got a new unsynced message "
@@ -1592,9 +1594,9 @@ public class MessagingController implements Runnable {
return true;
}
- private void downloadSmallMessages(final Account account, final Folder remoteFolder,
+ private void downloadSmallMessages(final Account account, final Folder remoteFolder,
final LocalFolder localFolder,
- List smallMessages,
+ List smallMessages,
final AtomicInteger progress,
final int unreadBeforeStart,
final AtomicInteger newMessages,
@@ -1608,9 +1610,9 @@ public class MessagingController implements Runnable {
Log.d(K9.LOG_TAG, "SYNC: Fetching small messages for folder " + folder);
remoteFolder.fetch(smallMessages,
- fp, new MessageRetrievalListener() {
+ fp, new MessageRetrievalListener() {
@Override
- public void messageFinished(final Message message, int number, int ofTotal) {
+ public void messageFinished(final T message, int number, int ofTotal) {
try {
if (!shouldImportMessage(account, folder, message, progress, earliestDate)) {
@@ -1620,7 +1622,7 @@ public class MessagingController implements Runnable {
}
// Store the updated message locally
- final Message localMessage = localFolder.storeSmallMessage(message, new Runnable() {
+ final LocalMessage localMessage = localFolder.storeSmallMessage(message, new Runnable() {
@Override
public void run() {
progress.incrementAndGet();
@@ -1671,9 +1673,9 @@ 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,
- List largeMessages,
+ List largeMessages,
final AtomicInteger progress,
final int unreadBeforeStart,
final AtomicInteger newMessages,
@@ -1744,7 +1746,7 @@ public class MessagingController implements Runnable {
* right now, attachments will be left for later.
*/
- Set viewables = MimeUtility.collectTextParts(message);
+ Set viewables = MessageExtractor.collectTextParts(message);
/*
* Now download the parts we're interested in storing.
@@ -1768,7 +1770,7 @@ public class MessagingController implements Runnable {
// Update the listener with what we've found
progress.incrementAndGet();
// TODO do we need to re-fetch this here?
- Message localMessage = localFolder.getMessage(message.getUid());
+ LocalMessage localMessage = localFolder.getMessage(message.getUid());
// Increment the number of "new messages" if the newly downloaded message is
// not marked as read.
@@ -1821,11 +1823,11 @@ public class MessagingController implements Runnable {
remoteFolder.fetch(undeletedMessages, fp, null);
for (Message remoteMessage : syncFlagMessages) {
- Message localMessage = localFolder.getMessage(remoteMessage.getUid());
+ LocalMessage localMessage = localFolder.getMessage(remoteMessage.getUid());
boolean messageChanged = syncFlags(localMessage, remoteMessage);
if (messageChanged) {
boolean shouldBeNotifiedOf = false;
- if (localMessage.isSet(Flag.DELETED) || isMessageSuppressed(account, localMessage)) {
+ if (localMessage.isSet(Flag.DELETED) || isMessageSuppressed(localMessage)) {
for (MessagingListener l : getListeners()) {
l.synchronizeMailboxRemovedMessage(account, folder, localMessage);
}
@@ -1859,13 +1861,13 @@ public class MessagingController implements Runnable {
}
}
- private boolean syncFlags(Message localMessage, Message remoteMessage) throws MessagingException {
+ private boolean syncFlags(LocalMessage localMessage, Message remoteMessage) throws MessagingException {
boolean messageChanged = false;
if (localMessage == null || localMessage.isSet(Flag.DELETED)) {
return false;
}
if (remoteMessage.isSet(Flag.DELETED)) {
- if (localMessage.getFolder().getAccount().syncRemoteDeletions()) {
+ if (localMessage.getFolder().syncRemoteDeletions()) {
localMessage.setFlag(Flag.DELETED, true);
messageChanged = true;
}
@@ -2722,7 +2724,7 @@ public class MessagingController implements Runnable {
long nowTime = System.currentTimeMillis();
Date nowDate = new Date(nowTime);
message.setInternalDate(nowDate);
- message.addSentDate(nowDate);
+ message.addSentDate(nowDate, K9.hideTimeZone());
message.setFrom(new Address(account.getEmail(), "K9mail internal"));
localFolder.appendMessages(Collections.singletonList(message));
@@ -2857,7 +2859,7 @@ public class MessagingController implements Runnable {
* @param newState
* {@code true}, if the flag should be set. {@code false} if it should be removed.
*/
- public void setFlag(Account account, String folderName, List messages, Flag flag,
+ public void setFlag(Account account, String folderName, List extends Message> messages, Flag flag,
boolean newState) {
// TODO: Put this into the background, but right now some callers depend on the message
// objects being modified right after this method returns.
@@ -3197,7 +3199,7 @@ public class MessagingController implements Runnable {
try {
LocalStore localStore = account.getLocalStore();
- List attachments = MimeUtility.collectAttachments(message);
+ List attachments = MessageExtractor.collectAttachments(message);
for (Part attachment : attachments) {
attachment.setBody(null);
}
@@ -3775,7 +3777,7 @@ public class MessagingController implements Runnable {
}
}
public void moveMessages(final Account account, final String srcFolder,
- final List messages, final String destFolder,
+ final List messages, final String destFolder,
final MessagingListener listener) {
suppressMessages(account, messages);
@@ -3790,7 +3792,7 @@ public class MessagingController implements Runnable {
}
public void moveMessagesInThread(final Account account, final String srcFolder,
- final List messages, final String destFolder) {
+ final List messages, final String destFolder) {
suppressMessages(account, messages);
@@ -3808,14 +3810,14 @@ public class MessagingController implements Runnable {
});
}
- public void moveMessage(final Account account, final String srcFolder, final Message message,
+ public void moveMessage(final Account account, final String srcFolder, final LocalMessage message,
final String destFolder, final MessagingListener listener) {
moveMessages(account, srcFolder, Collections.singletonList(message), destFolder, listener);
}
public void copyMessages(final Account account, final String srcFolder,
- final List messages, final String destFolder,
+ final List extends Message> messages, final String destFolder,
final MessagingListener listener) {
putBackground("copyMessages", null, new Runnable() {
@@ -3828,7 +3830,7 @@ public class MessagingController implements Runnable {
}
public void copyMessagesInThread(final Account account, final String srcFolder,
- final List messages, final String destFolder) {
+ final List extends Message> messages, final String destFolder) {
putBackground("copyMessagesInThread", null, new Runnable() {
@Override
@@ -3851,7 +3853,7 @@ public class MessagingController implements Runnable {
}
private void moveOrCopyMessageSynchronous(final Account account, final String srcFolder,
- final List inMessages, final String destFolder, final boolean isCopy,
+ final List extends Message> inMessages, final String destFolder, final boolean isCopy,
MessagingListener listener) {
try {
@@ -3962,7 +3964,7 @@ public class MessagingController implements Runnable {
localFolder.open(Folder.OPEN_MODE_RW);
String uid = localFolder.getMessageUidById(id);
if (uid != null) {
- Message message = localFolder.getMessage(uid);
+ LocalMessage message = localFolder.getMessage(uid);
if (message != null) {
deleteMessages(Collections.singletonList(message), null);
}
@@ -3974,7 +3976,7 @@ public class MessagingController implements Runnable {
}
}
- public void deleteThreads(final List messages) {
+ public void deleteThreads(final List messages) {
actOnMessages(messages, new MessageActor() {
@Override
@@ -4006,7 +4008,7 @@ public class MessagingController implements Runnable {
}
}
- public List collectMessagesInThreads(Account account, List messages)
+ public List collectMessagesInThreads(Account account, List extends Message> messages)
throws MessagingException {
LocalStore localStore = account.getLocalStore();
@@ -4025,7 +4027,7 @@ public class MessagingController implements Runnable {
return messagesInThreads;
}
- public void deleteMessages(final List messages, final MessagingListener listener) {
+ public void deleteMessages(final List messages, final MessagingListener listener) {
actOnMessages(messages, new MessageActor() {
@Override
@@ -4244,13 +4246,12 @@ public class MessagingController implements Runnable {
try {
Intent msg = new Intent(Intent.ACTION_SEND);
String quotedText = null;
- Part part = MimeUtility.findFirstPartByMimeType(message,
- "text/plain");
+ Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (part == null) {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
}
if (part != null) {
- quotedText = MimeUtility.getTextFromPart(part);
+ quotedText = MessageExtractor.getTextFromPart(part);
}
if (quotedText != null) {
msg.putExtra(Intent.EXTRA_TEXT, quotedText);
@@ -4393,7 +4394,6 @@ public class MessagingController implements Runnable {
Store localStore = account.getLocalStore();
for (final Folder folder : localStore.getPersonalNamespaces(false)) {
folder.open(Folder.OPEN_MODE_RW);
- folder.refresh(prefs);
Folder.FolderClass fDisplayClass = folder.getDisplayClass();
Folder.FolderClass fSyncClass = folder.getSyncClass();
@@ -4696,7 +4696,7 @@ public class MessagingController implements Runnable {
if (fromAddrs != null) {
isSelf = account.isAnIdentity(fromAddrs);
if (!isSelf && fromAddrs.length > 0) {
- return fromAddrs[0].toFriendly(contacts).toString();
+ return MessageHelper.toFriendly(fromAddrs[0], contacts).toString();
}
}
@@ -4706,7 +4706,7 @@ public class MessagingController implements Runnable {
if (rcpts != null && rcpts.length > 0) {
return context.getString(R.string.message_to_fmt,
- rcpts[0].toFriendly(contacts).toString());
+ MessageHelper.toFriendly(rcpts[0], contacts).toString());
}
return context.getString(R.string.general_no_sender);
@@ -4781,8 +4781,7 @@ public class MessagingController implements Runnable {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
- private Message findNewestMessageForNotificationLocked(Context context,
- Account account, NotificationData data) {
+ private LocalMessage findNewestMessageForNotificationLocked(Context context, NotificationData data) {
if (!data.messages.isEmpty()) {
return data.messages.getFirst();
}
@@ -4798,7 +4797,7 @@ public class MessagingController implements Runnable {
* Creates a notification of a newly received message.
*/
private void notifyAccount(Context context, Account account,
- Message message, int previousUnreadMessageCount) {
+ LocalMessage message, int previousUnreadMessageCount) {
final NotificationData data = getNotificationData(account, previousUnreadMessageCount);
synchronized (data) {
notifyAccountWithDataLocked(context, account, message, data);
@@ -4809,12 +4808,12 @@ public class MessagingController implements Runnable {
private static final int NUM_SENDERS_IN_LOCK_SCREEN_NOTIFICATION = 5;
private void notifyAccountWithDataLocked(Context context, Account account,
- Message message, NotificationData data) {
+ LocalMessage message, NotificationData data) {
boolean updateSilently = false;
if (message == null) {
/* this can happen if a message we previously notified for is read or deleted remotely */
- message = findNewestMessageForNotificationLocked(context, account, data);
+ message = findNewestMessageForNotificationLocked(context, data);
updateSilently = true;
if (message == null) {
// seemingly both the message list as well as the overflow list is empty;
@@ -5110,7 +5109,7 @@ public class MessagingController implements Runnable {
int unreadCount,
CharSequence accountDescription,
CharSequence formattedSender,
- List messages) {
+ List extends Message> messages) {
if (!platformSupportsLockScreenNotifications()) {
return;
}
@@ -5307,7 +5306,6 @@ public class MessagingController implements Runnable {
continue;
}
folder.open(Folder.OPEN_MODE_RW);
- folder.refresh(prefs);
Folder.FolderClass fDisplayClass = folder.getDisplayClass();
Folder.FolderClass fPushClass = folder.getPushClass();
@@ -5685,15 +5683,15 @@ public class MessagingController implements Runnable {
}
- private void actOnMessages(List messages, MessageActor actor) {
+ private void actOnMessages(List messages, MessageActor actor) {
Map>> accountMap = new HashMap>>();
- for (Message message : messages) {
+ for (LocalMessage message : messages) {
if ( message == null) {
continue;
}
Folder folder = message.getFolder();
- Account account = folder.getAccount();
+ Account account = message.getAccount();
Map> folderMap = accountMap.get(account);
if (folderMap == null) {
diff --git a/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java b/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java
index ccd13a59f..3bdec8c22 100644
--- a/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java
+++ b/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java
@@ -11,8 +11,8 @@ import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.PushReceiver;
-import com.fsck.k9.mail.store.local.LocalFolder;
-import com.fsck.k9.mail.store.local.LocalStore;
+import com.fsck.k9.mailstore.LocalFolder;
+import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.service.SleepService;
import java.util.List;
diff --git a/src/com/fsck/k9/controller/MessagingListener.java b/src/com/fsck/k9/controller/MessagingListener.java
index 5c5a66f50..72bd93d18 100644
--- a/src/com/fsck/k9/controller/MessagingListener.java
+++ b/src/com/fsck/k9/controller/MessagingListener.java
@@ -11,6 +11,7 @@ import com.fsck.k9.BaseAccount;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Part;
+import com.fsck.k9.mailstore.LocalMessage;
/**
* Defines the interface that {@link MessagingController} will use to callback to requesters.
@@ -42,10 +43,8 @@ public class MessagingListener {
public void listLocalMessagesStarted(Account account, String folder) {}
- public void listLocalMessages(Account account, String folder, Message[] messages) {}
-
public void listLocalMessagesAddMessages(Account account, String folder,
- List messages) {}
+ List messages) {}
public void listLocalMessagesUpdateMessage(Account account, String folder, Message message) {}
@@ -156,10 +155,9 @@ public class MessagingListener {
/**
* Called when a remote search is started
*
- * @param acct
* @param folder
*/
- public void remoteSearchStarted(Account acct, String folder) {}
+ public void remoteSearchStarted(String folder) {}
/**
@@ -167,35 +165,30 @@ public class MessagingListener {
*
* @param numResults
*/
- public void remoteSearchServerQueryComplete(Account account, String folderName, int numResults) { }
+ public void remoteSearchServerQueryComplete(String folderName, int numResults, int maxResults) { }
/**
* Called when a new result message is available for a remote search
* Can assume headers have been downloaded, but potentially not body.
- * @param account
* @param folder
* @param message
*/
- public void remoteSearchAddMessage(Account account, String folder, Message message, int numDone, int numTotal) { }
+ public void remoteSearchAddMessage(String folder, Message message, int numDone, int numTotal) { }
/**
* Called when Remote Search is fully complete
- *
- * @param acct
- * @param folder
+ * @param folder
* @param numResults
*/
- public void remoteSearchFinished(Account acct, String folder, int numResults, List extraResults) {}
+ public void remoteSearchFinished(String folder, int numResults, int maxResults, List extraResults) {}
/**
* Called when there was a problem with a remote search operation.
- *
- * @param acct
- * @param folder
+ * @param folder
* @param err
*/
- public void remoteSearchFailed(Account acct, String folder, String err) { }
+ public void remoteSearchFailed(String folder, String err) { }
/**
* General notification messages subclasses can override to be notified that the controller
diff --git a/src/com/fsck/k9/mail/store/UnavailableAccountException.java b/src/com/fsck/k9/controller/UnavailableAccountException.java
similarity index 83%
rename from src/com/fsck/k9/mail/store/UnavailableAccountException.java
rename to src/com/fsck/k9/controller/UnavailableAccountException.java
index 26e66b1be..55533828e 100644
--- a/src/com/fsck/k9/mail/store/UnavailableAccountException.java
+++ b/src/com/fsck/k9/controller/UnavailableAccountException.java
@@ -1,10 +1,8 @@
-package com.fsck.k9.mail.store;
-
-import com.fsck.k9.Account;
+package com.fsck.k9.controller;
/**
- * An {@link Account} is not
- * {@link Account#isAvailable(android.content.Context)}.
+ * An {@link com.fsck.k9.Account} is not
+ * {@link com.fsck.k9.Account#isAvailable(android.content.Context)}.
* The operation may be retried later.
*/
public class UnavailableAccountException extends RuntimeException {
diff --git a/src/com/fsck/k9/crypto/CryptoHelper.java b/src/com/fsck/k9/crypto/CryptoHelper.java
index 3ce2787c7..612c4ba3d 100644
--- a/src/com/fsck/k9/crypto/CryptoHelper.java
+++ b/src/com/fsck/k9/crypto/CryptoHelper.java
@@ -7,8 +7,10 @@ import java.util.regex.Pattern;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
+import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeUtility;
+
public class CryptoHelper {
public static Pattern PGP_MESSAGE =
@@ -37,7 +39,7 @@ public class CryptoHelper {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
}
if (part != null) {
- data = MimeUtility.getTextFromPart(part);
+ data = MessageExtractor.getTextFromPart(part);
}
} catch (MessagingException e) {
// guess not...
@@ -60,7 +62,7 @@ public class CryptoHelper {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
}
if (part != null) {
- data = MimeUtility.getTextFromPart(part);
+ data = MessageExtractor.getTextFromPart(part);
}
} catch (MessagingException e) {
// guess not...
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index 80b19b33a..c14c4fc96 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -88,8 +88,9 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
-import com.fsck.k9.mail.store.local.LocalFolder;
-import com.fsck.k9.mail.store.local.LocalStore;
+import com.fsck.k9.mailstore.LocalFolder;
+import com.fsck.k9.mailstore.LocalMessage;
+import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.provider.EmailProvider.SpecialColumns;
@@ -427,7 +428,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* Relevant messages for the current context when we have to remember the chosen messages
* between user interactions (e.g. selecting a folder for move operation).
*/
- private List mActiveMessages;
+ private List mActiveMessages;
/* package visibility for faster inner class access */
MessageHelper mMessageHelper;
@@ -1035,7 +1036,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
return null;
}
- private Folder getFolderById(Account account, long folderId) {
+ private LocalFolder getFolderById(Account account, long folderId) {
try {
LocalStore localStore = account.getLocalStore();
LocalFolder localFolder = localStore.getFolderById(folderId);
@@ -1189,19 +1190,19 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
}
- public void onReply(Message message) {
+ public void onReply(LocalMessage message) {
mFragmentListener.onReply(message);
}
- public void onReplyAll(Message message) {
+ public void onReplyAll(LocalMessage message) {
mFragmentListener.onReplyAll(message);
}
- public void onForward(Message message) {
+ public void onForward(LocalMessage message) {
mFragmentListener.onForward(message);
}
- public void onResendMessage(Message message) {
+ public void onResendMessage(LocalMessage message) {
mFragmentListener.onResendMessage(message);
}
@@ -1309,11 +1310,11 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
changeSort(sorts[curIndex]);
}
- private void onDelete(Message message) {
+ private void onDelete(LocalMessage message) {
onDelete(Collections.singletonList(message));
}
- private void onDelete(List messages) {
+ private void onDelete(List messages) {
if (K9.confirmDelete()) {
// remember the message selection for #onCreateDialog(int)
mActiveMessages = messages;
@@ -1323,7 +1324,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
}
- private void onDeleteConfirmed(List messages) {
+ private void onDeleteConfirmed(List messages) {
if (mThreadedList) {
mController.deleteThreads(messages);
} else {
@@ -1345,15 +1346,14 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
final String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER);
- final List messages = mActiveMessages;
+ final List messages = mActiveMessages;
if (destFolderName != null) {
mActiveMessages = null; // don't need it any more
if (messages.size() > 0) {
- Account account = messages.get(0).getFolder().getAccount();
- account.setLastSelectedFolderName(destFolderName);
+ messages.get(0).getFolder().setLastSelectedFolderName(destFolderName);
}
switch (requestCode) {
@@ -1510,23 +1510,19 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
break;
}
case R.id.reply: {
- Message message = getMessageAtPosition(adapterPosition);
- onReply(message);
+ onReply(getMessageAtPosition(adapterPosition));
break;
}
case R.id.reply_all: {
- Message message = getMessageAtPosition(adapterPosition);
- onReplyAll(message);
+ onReplyAll(getMessageAtPosition(adapterPosition));
break;
}
case R.id.forward: {
- Message message = getMessageAtPosition(adapterPosition);
- onForward(message);
+ onForward(getMessageAtPosition(adapterPosition));
break;
}
case R.id.send_again: {
- Message message = getMessageAtPosition(adapterPosition);
- onResendMessage(message);
+ onResendMessage(getMessageAtPosition(adapterPosition));
mSelectedCount = 0;
break;
}
@@ -1539,7 +1535,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
break;
}
case R.id.delete: {
- Message message = getMessageAtPosition(adapterPosition);
+ LocalMessage message = getMessageAtPosition(adapterPosition);
onDelete(message);
break;
}
@@ -1562,23 +1558,19 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
// only if the account supports this
case R.id.archive: {
- Message message = getMessageAtPosition(adapterPosition);
- onArchive(message);
+ onArchive(getMessageAtPosition(adapterPosition));
break;
}
case R.id.spam: {
- Message message = getMessageAtPosition(adapterPosition);
- onSpam(message);
+ onSpam(getMessageAtPosition(adapterPosition));
break;
}
case R.id.move: {
- Message message = getMessageAtPosition(adapterPosition);
- onMove(message);
+ onMove(getMessageAtPosition(adapterPosition));
break;
}
case R.id.copy: {
- Message message = getMessageAtPosition(adapterPosition);
- onCopy(message);
+ onCopy(getMessageAtPosition(adapterPosition));
break;
}
}
@@ -1711,7 +1703,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
class MessageListActivityListener extends ActivityListener {
@Override
- public void remoteSearchFailed(Account acct, String folder, final String err) {
+ public void remoteSearchFailed(String folder, final String err) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -1725,7 +1717,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
@Override
- public void remoteSearchStarted(Account acct, String folder) {
+ public void remoteSearchStarted(String folder) {
mHandler.progress(true);
mHandler.updateFooter(mContext.getString(R.string.remote_search_sending_query));
}
@@ -1736,12 +1728,12 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
@Override
- public void remoteSearchFinished(Account acct, String folder, int numResults, List extraResults) {
+ public void remoteSearchFinished(String folder, int numResults, int maxResults, List extraResults) {
mHandler.progress(false);
mHandler.remoteSearchFinished();
mExtraSearchResults = extraResults;
if (extraResults != null && extraResults.size() > 0) {
- mHandler.updateFooter(String.format(mContext.getString(R.string.load_more_messages_fmt), acct.getRemoteSearchNumResults()));
+ mHandler.updateFooter(String.format(mContext.getString(R.string.load_more_messages_fmt), maxResults));
} else {
mHandler.updateFooter("");
}
@@ -1750,11 +1742,11 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
@Override
- public void remoteSearchServerQueryComplete(Account account, String folderName, int numResults) {
+ public void remoteSearchServerQueryComplete(String folderName, int numResults, int maxResults) {
mHandler.progress(true);
- if (account != null && account.getRemoteSearchNumResults() != 0 && numResults > account.getRemoteSearchNumResults()) {
+ if (maxResults != 0 && numResults > maxResults) {
mHandler.updateFooter(mContext.getString(R.string.remote_search_downloading_limited,
- account.getRemoteSearchNumResults(), numResults));
+ maxResults, numResults));
} else {
mHandler.updateFooter(mContext.getString(R.string.remote_search_downloading, numResults));
}
@@ -2416,7 +2408,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
computeBatchDirection();
}
- private void onMove(Message message) {
+ private void onMove(LocalMessage message) {
onMove(Collections.singletonList(message));
}
@@ -2426,7 +2418,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param messages
* Never {@code null}.
*/
- private void onMove(List messages) {
+ private void onMove(List messages) {
if (!checkCopyOrMovePossible(messages, FolderOperation.MOVE)) {
return;
}
@@ -2440,12 +2432,13 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
folder = null;
}
- Account account = messages.get(0).getFolder().getAccount();
- displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, account, folder, messages);
+ displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, folder,
+ messages.get(0).getFolder().getAccountUuid(), null,
+ messages);
}
- private void onCopy(Message message) {
+ private void onCopy(LocalMessage message) {
onCopy(Collections.singletonList(message));
}
@@ -2455,7 +2448,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param messages
* Never {@code null}.
*/
- private void onCopy(List messages) {
+ private void onCopy(List messages) {
if (!checkCopyOrMovePossible(messages, FolderOperation.COPY)) {
return;
}
@@ -2469,7 +2462,10 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
folder = null;
}
- displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, mAccount, folder, messages);
+ displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, folder,
+ messages.get(0).getFolder().getAccountUuid(),
+ null,
+ messages);
}
/**
@@ -2486,12 +2482,13 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
*
* @see #startActivityForResult(Intent, int)
*/
- private void displayFolderChoice(int requestCode, Account account, Folder folder,
- List messages) {
+ private void displayFolderChoice(int requestCode, Folder folder,
+ String accountUuid, String lastSelectedFolderName,
+ List messages) {
Intent intent = new Intent(getActivity(), ChooseFolder.class);
- intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, account.getUuid());
- intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, account.getLastSelectedFolderName());
+ intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, accountUuid);
+ intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, lastSelectedFolderName);
if (folder == null) {
intent.putExtra(ChooseFolder.EXTRA_SHOW_CURRENT, "yes");
@@ -2504,14 +2501,14 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
startActivityForResult(intent, requestCode);
}
- private void onArchive(final Message message) {
+ private void onArchive(final LocalMessage message) {
onArchive(Collections.singletonList(message));
}
- private void onArchive(final List messages) {
- Map> messagesByAccount = groupMessagesByAccount(messages);
+ private void onArchive(final List messages) {
+ Map> messagesByAccount = groupMessagesByAccount(messages);
- for (Entry> entry : messagesByAccount.entrySet()) {
+ for (Entry> entry : messagesByAccount.entrySet()) {
Account account = entry.getKey();
String archiveFolder = account.getArchiveFolderName();
@@ -2521,14 +2518,14 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
}
- private Map> groupMessagesByAccount(final List messages) {
- Map> messagesByAccount = new HashMap>();
- for (Message message : messages) {
- Account account = message.getFolder().getAccount();
+ private Map> groupMessagesByAccount(final List messages) {
+ Map> messagesByAccount = new HashMap>();
+ for (LocalMessage message : messages) {
+ Account account = message.getAccount();
- List msgList = messagesByAccount.get(account);
+ List msgList = messagesByAccount.get(account);
if (msgList == null) {
- msgList = new ArrayList();
+ msgList = new ArrayList();
messagesByAccount.put(account, msgList);
}
@@ -2537,7 +2534,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
return messagesByAccount;
}
- private void onSpam(Message message) {
+ private void onSpam(LocalMessage message) {
onSpam(Collections.singletonList(message));
}
@@ -2547,7 +2544,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param messages
* The messages to move to the spam folder. Never {@code null}.
*/
- private void onSpam(List messages) {
+ private void onSpam(List messages) {
if (K9.confirmSpam()) {
// remember the message selection for #onCreateDialog(int)
mActiveMessages = messages;
@@ -2557,10 +2554,10 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
}
- private void onSpamConfirmed(List messages) {
- Map> messagesByAccount = groupMessagesByAccount(messages);
+ private void onSpamConfirmed(List messages) {
+ Map> messagesByAccount = groupMessagesByAccount(messages);
- for (Entry> entry : messagesByAccount.entrySet()) {
+ for (Entry> entry : messagesByAccount.entrySet()) {
Account account = entry.getKey();
String spamFolder = account.getSpamFolderName();
@@ -2584,7 +2581,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
*
* @return {@code true}, if operation is possible.
*/
- private boolean checkCopyOrMovePossible(final List messages,
+ private boolean checkCopyOrMovePossible(final List messages,
final FolderOperation operation) {
if (messages.isEmpty()) {
@@ -2592,11 +2589,11 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
boolean first = true;
- for (final Message message : messages) {
+ for (final LocalMessage message : messages) {
if (first) {
first = false;
// account check
- final Account account = message.getFolder().getAccount();
+ final Account account = message.getAccount();
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(account)) ||
(operation == FolderOperation.COPY && !mController.isCopyCapable(account))) {
return false;
@@ -2622,7 +2619,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param destination
* The name of the destination folder. Never {@code null}.
*/
- private void copy(List messages, final String destination) {
+ private void copy(List messages, final String destination) {
copyOrMove(messages, destination, FolderOperation.COPY);
}
@@ -2634,7 +2631,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param destination
* The name of the destination folder. Never {@code null}.
*/
- private void move(List messages, final String destination) {
+ private void move(List messages, final String destination) {
copyOrMove(messages, destination, FolderOperation.MOVE);
}
@@ -2650,12 +2647,12 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param operation
* Specifies what operation to perform. Never {@code null}.
*/
- private void copyOrMove(List messages, final String destination,
+ private void copyOrMove(List messages, final String destination,
final FolderOperation operation) {
- Map> folderMap = new HashMap>();
+ Map> folderMap = new HashMap>();
- for (Message message : messages) {
+ for (LocalMessage message : messages) {
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(message)) ||
(operation == FolderOperation.COPY && !mController.isCopyCapable(message))) {
@@ -2674,19 +2671,19 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
continue;
}
- List outMessages = folderMap.get(folderName);
+ List outMessages = folderMap.get(folderName);
if (outMessages == null) {
- outMessages = new ArrayList();
+ outMessages = new ArrayList();
folderMap.put(folderName, outMessages);
}
outMessages.add(message);
}
- for (Map.Entry> entry : folderMap.entrySet()) {
+ for (Map.Entry> entry : folderMap.entrySet()) {
String folderName = entry.getKey();
- List outMessages = entry.getValue();
- Account account = outMessages.get(0).getFolder().getAccount();
+ List outMessages = entry.getValue();
+ Account account = outMessages.get(0).getAccount();
if (operation == FolderOperation.MOVE) {
if (mThreadedList) {
@@ -2859,7 +2856,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
*/
switch (item.getItemId()) {
case R.id.delete: {
- List messages = getCheckedMessages();
+ List messages = getCheckedMessages();
onDelete(messages);
mSelectedCount = 0;
break;
@@ -2887,26 +2884,22 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
// only if the account supports this
case R.id.archive: {
- List messages = getCheckedMessages();
- onArchive(messages);
+ onArchive(getCheckedMessages());
mSelectedCount = 0;
break;
}
case R.id.spam: {
- List messages = getCheckedMessages();
- onSpam(messages);
+ onSpam(getCheckedMessages());
mSelectedCount = 0;
break;
}
case R.id.move: {
- List messages = getCheckedMessages();
- onMove(messages);
+ onMove(getCheckedMessages());
mSelectedCount = 0;
break;
}
case R.id.copy: {
- List messages = getCheckedMessages();
- onCopy(messages);
+ onCopy(getCheckedMessages());
mSelectedCount = 0;
break;
}
@@ -2987,7 +2980,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
final Folder remoteFolder = mCurrentFolder.folder;
remoteFolder.close();
// Send a remoteSearchFinished() message for good measure.
- mListener.remoteSearchFinished(searchAccount, mCurrentFolder.name, 0, null);
+ mListener.remoteSearchFinished(mCurrentFolder.name, 0, searchAccount.getRemoteSearchNumResults(), null);
} catch (Exception e) {
// Since the user is going back, log and squash any exceptions.
Log.e(K9.LOG_TAG, "Could not abort remote search before going back", e);
@@ -3116,10 +3109,10 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
void setMessageListProgress(int level);
void showThread(Account account, String folderName, long rootId);
void showMoreFromSameSender(String senderAddress);
- void onResendMessage(Message message);
- void onForward(Message message);
- void onReply(Message message);
- void onReplyAll(Message message);
+ void onResendMessage(LocalMessage message);
+ void onForward(LocalMessage message);
+ void onReply(LocalMessage message);
+ void onReplyAll(LocalMessage message);
void openMessage(MessageReference messageReference);
void setMessageListTitle(String title);
void setMessageListSubTitle(String subTitle);
@@ -3135,7 +3128,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
changeSort(mSortType);
}
- private Message getSelectedMessage() {
+ private LocalMessage getSelectedMessage() {
int listViewPosition = mListView.getSelectedItemPosition();
int adapterPosition = listViewToAdapterPosition(listViewPosition);
@@ -3158,7 +3151,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
return AdapterView.INVALID_POSITION;
}
- private Message getMessageAtPosition(int adapterPosition) {
+ private LocalMessage getMessageAtPosition(int adapterPosition) {
if (adapterPosition == AdapterView.INVALID_POSITION) {
return null;
}
@@ -3168,7 +3161,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
Account account = getAccountFromCursor(cursor);
long folderId = cursor.getLong(FOLDER_ID_COLUMN);
- Folder folder = getFolderById(account, folderId);
+ LocalFolder folder = getFolderById(account, folderId);
try {
return folder.getMessage(uid);
@@ -3179,14 +3172,14 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
return null;
}
- private List getCheckedMessages() {
- List messages = new ArrayList(mSelected.size());
+ private List getCheckedMessages() {
+ List messages = new ArrayList(mSelected.size());
for (int position = 0, end = mAdapter.getCount(); position < end; position++) {
Cursor cursor = (Cursor) mAdapter.getItem(position);
long uniqueId = cursor.getLong(mUniqueIdColumn);
if (mSelected.contains(uniqueId)) {
- Message message = getMessageAtPosition(position);
+ LocalMessage message = getMessageAtPosition(position);
if (message != null) {
messages.add(message);
}
@@ -3197,7 +3190,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
public void onDelete() {
- Message message = getSelectedMessage();
+ LocalMessage message = getSelectedMessage();
if (message != null) {
onDelete(Collections.singletonList(message));
}
@@ -3227,21 +3220,21 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
public void onMove() {
- Message message = getSelectedMessage();
+ LocalMessage message = getSelectedMessage();
if (message != null) {
onMove(message);
}
}
public void onArchive() {
- Message message = getSelectedMessage();
+ LocalMessage message = getSelectedMessage();
if (message != null) {
onArchive(message);
}
}
public void onCopy() {
- Message message = getSelectedMessage();
+ LocalMessage message = getSelectedMessage();
if (message != null) {
onCopy(message);
}
diff --git a/src/com/fsck/k9/fragment/MessageViewFragment.java b/src/com/fsck/k9/fragment/MessageViewFragment.java
index 3a8bb4ddd..961a38555 100644
--- a/src/com/fsck/k9/fragment/MessageViewFragment.java
+++ b/src/com/fsck/k9/fragment/MessageViewFragment.java
@@ -38,7 +38,7 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
-import com.fsck.k9.mail.store.local.LocalMessage;
+import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.view.AttachmentView;
import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback;
import com.fsck.k9.view.MessageHeader;
@@ -75,7 +75,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
private PgpData mPgpData;
private Account mAccount;
private MessageReference mMessageReference;
- private Message mMessage;
+ private LocalMessage mMessage;
private MessagingController mController;
private Listener mListener = new Listener();
private MessageViewHandler mHandler = new MessageViewHandler();
@@ -311,7 +311,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
// Disable the delete button after it's tapped (to try to prevent
// accidental clicks)
mFragmentListener.disableDeleteAction();
- Message messageToDelete = mMessage;
+ LocalMessage messageToDelete = mMessage;
mFragmentListener.showNextMessageOrReturn();
mController.deleteMessages(Collections.singletonList(messageToDelete), null);
}
@@ -341,7 +341,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
private void refileMessage(String dstFolder) {
String srcFolder = mMessageReference.folderName;
- Message messageToMove = mMessage;
+ LocalMessage messageToMove = mMessage;
mFragmentListener.showNextMessageOrReturn();
mController.moveMessage(mAccount, srcFolder, messageToMove, dstFolder, null);
}
@@ -576,7 +576,8 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
@Override
public void loadMessageForViewBodyAvailable(final Account account, String folder,
String uid, final Message message) {
- if (!mMessageReference.uid.equals(uid) ||
+ if (!(message instanceof LocalMessage) ||
+ !mMessageReference.uid.equals(uid) ||
!mMessageReference.folderName.equals(folder) ||
!mMessageReference.accountUuid.equals(account.getUuid())) {
return;
@@ -586,7 +587,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
@Override
public void run() {
try {
- mMessage = message;
+ mMessage = (LocalMessage) message;
mMessageView.setMessage(account, (LocalMessage) message, mPgpData,
mController, mListener);
mFragmentListener.updateMenu();
@@ -845,10 +846,10 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
}
public interface MessageViewFragmentListener {
- public void onForward(Message mMessage, PgpData mPgpData);
+ public void onForward(LocalMessage mMessage, PgpData mPgpData);
public void disableDeleteAction();
- public void onReplyAll(Message mMessage, PgpData mPgpData);
- public void onReply(Message mMessage, PgpData mPgpData);
+ public void onReplyAll(LocalMessage mMessage, PgpData mPgpData);
+ public void onReply(LocalMessage mMessage, PgpData mPgpData);
public void displayMessageSubject(String title);
public void setProgress(boolean b);
public void showNextMessageOrReturn();
diff --git a/src/com/fsck/k9/helper/HtmlConverter.java b/src/com/fsck/k9/helper/HtmlConverter.java
index 14eb75fda..28bf875b4 100644
--- a/src/com/fsck/k9/helper/HtmlConverter.java
+++ b/src/com/fsck/k9/helper/HtmlConverter.java
@@ -4,6 +4,7 @@ import android.text.*;
import android.text.Html.TagHandler;
import android.util.Log;
import com.fsck.k9.K9;
+
import org.xml.sax.XMLReader;
import java.io.IOException;
@@ -13,11 +14,75 @@ import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Contains common routines to convert html to text and vice versa.
*/
public class HtmlConverter {
+ /* This comprises most common used Unicode characters allowed in IRI
+ * as detailed in RFC 3987.
+ * Specifically, those two byte Unicode characters are not included.
+ */
+ private static final String GOOD_IRI_CHAR =
+ "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
+ /**
+ * Goegular expression to match all IANA top-level domains for WEB_URL.
+ * List accurate as of 2011/01/12. List taken from:
+ * http://data.iana.org/TLD/tlds-alpha-by-domain.txt
+ * This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py
+ */
+ private static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
+ "(?:"
+ + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+ + "|(?:biz|b[abdefghijmnorstvwyz])"
+ + "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
+ + "|d[ejkmoz]"
+ + "|(?:edu|e[cegrstu])"
+ + "|f[ijkmor]"
+ + "|(?:gov|g[abdefghilmnpqrstuwy])"
+ + "|h[kmnrtu]"
+ + "|(?:info|int|i[delmnoqrst])"
+ + "|(?:jobs|j[emop])"
+ + "|k[eghimnprwyz]"
+ + "|l[abcikrstuvy]"
+ + "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
+ + "|(?:name|net|n[acefgilopruz])"
+ + "|(?:org|om)"
+ + "|(?:pro|p[aefghklmnrstwy])"
+ + "|qa"
+ + "|r[eosuw]"
+ + "|s[abcdeghijklmnortuvyz]"
+ + "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
+ + "|u[agksyz]"
+ + "|v[aceginu]"
+ + "|w[fs]"
+ + "|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah)"
+ + "|y[et]"
+ + "|z[amw]))";
+ private static final String BITCOIN_URI_PATTERN =
+ "bitcoin:[1-9a-km-zA-HJ-NP-Z]{27,34}(\\?[a-zA-Z0-9$\\-_.+!*'(),%:@&=]*)?";
+ /**
+ * Regular expression pattern to match most part of RFC 3987
+ * Internationalized URLs, aka IRIs. Commonly used Unicode characters are
+ * added.
+ */
+ private static final Pattern WEB_URL_PATTERN = Pattern.compile(
+ "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+ + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+ + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
+ + "((?:(?:[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host
+ + TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
+ + "|(?:(?:25[0-5]|2[0-4]" // or ip address
+ + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
+ + "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
+ + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+ + "|[1-9][0-9]|[0-9])))"
+ + "(?:\\:\\d{1,5})?)" // plus option port number
+ + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
+ + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+ + "(?:\\b|$)"); // and finally, a word boundary or end of
+
/**
* When generating previews, Spannable objects that can't be converted into a String are
* represented as 0xfffc. When displayed, these show up as undisplayed squares. These constants
@@ -381,9 +446,9 @@ public class HtmlConverter {
* @param outputBuffer Buffer to append linked text to.
*/
protected static void linkifyText(final String text, final StringBuffer outputBuffer) {
- String prepared = text.replaceAll(Regex.BITCOIN_URI_PATTERN, "$0");
+ String prepared = text.replaceAll(BITCOIN_URI_PATTERN, "$0");
- Matcher m = Regex.WEB_URL_PATTERN.matcher(prepared);
+ Matcher m = WEB_URL_PATTERN.matcher(prepared);
while (m.find()) {
int start = m.start();
if (start == 0 || (start != 0 && prepared.charAt(start - 1) != '@')) {
diff --git a/src/com/fsck/k9/helper/MessageHelper.java b/src/com/fsck/k9/helper/MessageHelper.java
index 26430b9e8..ddc2f4a4d 100644
--- a/src/com/fsck/k9/helper/MessageHelper.java
+++ b/src/com/fsck/k9/helper/MessageHelper.java
@@ -1,7 +1,11 @@
package com.fsck.k9.helper;
import android.content.Context;
+import android.text.Spannable;
+import android.text.SpannableString;
import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
import android.util.Log;
import com.fsck.k9.Account;
@@ -11,11 +15,23 @@ import com.fsck.k9.activity.FolderInfoHolder;
import com.fsck.k9.activity.MessageInfoHolder;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
-import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Message.RecipientType;
+import com.fsck.k9.mailstore.LocalMessage;
public class MessageHelper {
+ /**
+ * If the number of addresses exceeds this value the addresses aren't
+ * resolved to the names of Android contacts.
+ *
+ *
+ * TODO: This number was chosen arbitrarily and should be determined by
+ * performance tests.
+ *
+ *
+ * @see #toFriendly(Address[], com.fsck.k9.helper.Contacts)
+ */
+ private static final int TOO_MANY_ADDRESSES = 50;
private static MessageHelper sInstance;
@@ -32,8 +48,10 @@ public class MessageHelper {
mContext = context;
}
- public void populate(final MessageInfoHolder target, final Message message,
- final FolderInfoHolder folder, final Account account) {
+ public void populate(final MessageInfoHolder target,
+ final LocalMessage message,
+ final FolderInfoHolder folder,
+ Account account) {
final Contacts contactHelper = K9.showContactName() ? Contacts.getInstance(mContext) : null;
try {
target.message = message;
@@ -53,11 +71,11 @@ public class MessageHelper {
Address[] addrs = message.getFrom();
if (addrs.length > 0 && account.isAnIdentity(addrs[0])) {
- CharSequence to = Address.toFriendly(message .getRecipients(RecipientType.TO), contactHelper);
+ CharSequence to = toFriendly(message.getRecipients(RecipientType.TO), contactHelper);
target.compareCounterparty = to.toString();
target.sender = new SpannableStringBuilder(mContext.getString(R.string.message_to_label)).append(to);
} else {
- target.sender = Address.toFriendly(addrs, contactHelper);
+ target.sender = toFriendly(addrs, contactHelper);
target.compareCounterparty = target.sender.toString();
}
@@ -68,14 +86,9 @@ public class MessageHelper {
target.senderAddress = target.compareCounterparty;
}
-
-
-
target.uid = message.getUid();
-
- target.account = account.getUuid();
- target.uri = "email://messages/" + account.getAccountNumber() + "/" + message.getFolder().getName() + "/" + message.getUid();
-
+ target.account = message.getFolder().getAccountUuid();
+ target.uri = message.getUri();
} catch (MessagingException me) {
Log.w(K9.LOG_TAG, "Unable to load message info", me);
}
@@ -86,11 +99,11 @@ public class MessageHelper {
CharSequence displayName;
if (fromAddrs.length > 0 && account.isAnIdentity(fromAddrs[0])) {
- CharSequence to = Address.toFriendly(toAddrs, contactHelper);
+ CharSequence to = toFriendly(toAddrs, contactHelper);
displayName = new SpannableStringBuilder(
mContext.getString(R.string.message_to_label)).append(to);
} else {
- displayName = Address.toFriendly(fromAddrs, contactHelper);
+ displayName = toFriendly(fromAddrs, contactHelper);
}
return displayName;
@@ -104,4 +117,69 @@ public class MessageHelper {
}
return false;
}
+
+ /**
+ * Returns the name of the contact this email address belongs to if
+ * the {@link Contacts contacts} parameter is not {@code null} and a
+ * contact is found. Otherwise the personal portion of the {@link Address}
+ * is returned. If that isn't available either, the email address is
+ * returned.
+ *
+ * @param address An {@link com.fsck.k9.mail.Address}
+ * @param contacts A {@link Contacts} instance or {@code null}.
+ * @return A "friendly" name for this {@link Address}.
+ */
+ public static CharSequence toFriendly(Address address, Contacts contacts) {
+ return toFriendly(address,contacts,
+ K9.showCorrespondentNames(),
+ K9.changeContactNameColor(),
+ K9.getContactNameColor());
+ }
+
+ public static CharSequence toFriendly(Address[] addresses, Contacts contacts) {
+ if (addresses == null) {
+ return null;
+ }
+
+ if (addresses.length >= TOO_MANY_ADDRESSES) {
+ // Don't look up contacts if the number of addresses is very high.
+ contacts = null;
+ }
+
+ SpannableStringBuilder sb = new SpannableStringBuilder();
+ for (int i = 0; i < addresses.length; i++) {
+ sb.append(toFriendly(addresses[i], contacts));
+ if (i < addresses.length - 1) {
+ sb.append(',');
+ }
+ }
+ return sb;
+ }
+
+ /* package, for testing */ static CharSequence toFriendly(Address address, Contacts contacts,
+ boolean showCorrespondentNames,
+ boolean changeContactNameColor,
+ int contactNameColor) {
+ if (!showCorrespondentNames) {
+ return address.getAddress();
+ } else if (contacts != null) {
+ final String name = contacts.getNameForAddress(address.getAddress());
+ // TODO: The results should probably be cached for performance reasons.
+ if (name != null) {
+ if (changeContactNameColor) {
+ final SpannableString coloredName = new SpannableString(name);
+ coloredName.setSpan(new ForegroundColorSpan(contactNameColor),
+ 0,
+ coloredName.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
+ );
+ return coloredName;
+ } else {
+ return name;
+ }
+ }
+ }
+
+ return (!TextUtils.isEmpty(address.getPersonal())) ? address.getPersonal() : address.getAddress();
+ }
}
diff --git a/src/com/fsck/k9/helper/Regex.java b/src/com/fsck/k9/helper/Regex.java
deleted file mode 100644
index dd60134a0..000000000
--- a/src/com/fsck/k9/helper/Regex.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Imported from AOSP on 2011-01-12 by JRV.
- * Domain patterns updated from IANA on 2010-01-12
- *
- *
- */
-
-package com.fsck.k9.helper;
-
-import java.util.regex.Pattern;
-
-/**
- * Commonly used regular expression patterns.
- */
-public class Regex {
-
- /**
- * Goegular expression to match all IANA top-level domains for WEB_URL.
- * List accurate as of 2011/01/12. List taken from:
- * http://data.iana.org/TLD/tlds-alpha-by-domain.txt
- * This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py
- */
- public static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
- "(?:"
- + "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
- + "|(?:biz|b[abdefghijmnorstvwyz])"
- + "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
- + "|d[ejkmoz]"
- + "|(?:edu|e[cegrstu])"
- + "|f[ijkmor]"
- + "|(?:gov|g[abdefghilmnpqrstuwy])"
- + "|h[kmnrtu]"
- + "|(?:info|int|i[delmnoqrst])"
- + "|(?:jobs|j[emop])"
- + "|k[eghimnprwyz]"
- + "|l[abcikrstuvy]"
- + "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
- + "|(?:name|net|n[acefgilopruz])"
- + "|(?:org|om)"
- + "|(?:pro|p[aefghklmnrstwy])"
- + "|qa"
- + "|r[eosuw]"
- + "|s[abcdeghijklmnortuvyz]"
- + "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
- + "|u[agksyz]"
- + "|v[aceginu]"
- + "|w[fs]"
- + "|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah)"
- + "|y[et]"
- + "|z[amw]))";
-
- /* This comprises most common used Unicode characters allowed in IRI
- * as detailed in RFC 3987.
- * Specifically, those two byte Unicode characters are not included.
- */
- public static final String GOOD_IRI_CHAR =
- "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
-
- /**
- * Regular expression pattern to match most part of RFC 3987
- * Internationalized URLs, aka IRIs. Commonly used Unicode characters are
- * added.
- */
- public static final Pattern WEB_URL_PATTERN = Pattern.compile(
- "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
- + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
- + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
- + "((?:(?:[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host
- + TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
- + "|(?:(?:25[0-5]|2[0-4]" // or ip address
- + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
- + "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
- + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
- + "|[1-9][0-9]|[0-9])))"
- + "(?:\\:\\d{1,5})?)" // plus option port number
- + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
- + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
- + "(?:\\b|$)"); // and finally, a word boundary or end of
- // input. This is to stop foo.sure from
- // matching as foo.su
-
- public static final Pattern EMAIL_ADDRESS_PATTERN
- = Pattern.compile(
- "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
- "\\@" +
- "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
- "(" +
- "\\." +
- "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
- ")+"
- );
-
- public static final String BITCOIN_URI_PATTERN =
- "bitcoin:[1-9a-km-zA-HJ-NP-Z]{27,34}(\\?[a-zA-Z0-9$\\-_.+!*'(),%:@&=]*)?";
-}
diff --git a/src/com/fsck/k9/helper/Utility.java b/src/com/fsck/k9/helper/Utility.java
index 50ca99e3a..ffd6270a2 100644
--- a/src/com/fsck/k9/helper/Utility.java
+++ b/src/com/fsck/k9/helper/Utility.java
@@ -15,7 +15,6 @@ import android.widget.EditText;
import android.widget.TextView;
import com.fsck.k9.K9;
-import com.fsck.k9.mail.filter.Base64;
import java.nio.charset.Charset;
import java.util.ArrayList;
@@ -91,22 +90,6 @@ public class Utility {
return TextUtils.join(String.valueOf(separator), parts);
}
- public static String base64Decode(String encoded) {
- if (encoded == null) {
- return null;
- }
- byte[] decoded = new Base64().decode(encoded.getBytes());
- return new String(decoded);
- }
-
- public static String base64Encode(String s) {
- if (s == null) {
- return s;
- }
- byte[] encoded = new Base64().encode(s.getBytes());
- return new String(encoded);
- }
-
public static boolean requiredFieldValid(TextView view) {
return view.getText() != null && view.getText().length() > 0;
}
@@ -130,48 +113,6 @@ public class Utility {
return false;
}
- private static final Pattern ATOM = Pattern.compile("^(?:[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]|\\s)+$");
-
- /**
- * Quote a string, if necessary, based upon the definition of an "atom," as defined by RFC2822
- * (http://tools.ietf.org/html/rfc2822#section-3.2.4). Strings that consist purely of atoms are
- * left unquoted; anything else is returned as a quoted string.
- * @param text String to quote.
- * @return Possibly quoted string.
- */
- public static String quoteAtoms(final String text) {
- if (ATOM.matcher(text).matches()) {
- return text;
- } else {
- return quoteString(text);
- }
- }
-
- /**
- * Ensures that the given string starts and ends with the double quote character. The string is not modified in any way except to add the
- * double quote character to start and end if it's not already there.
- * sample -> "sample"
- * "sample" -> "sample"
- * ""sample"" -> "sample"
- * "sample"" -> "sample"
- * sa"mp"le -> "sa"mp"le"
- * "sa"mp"le" -> "sa"mp"le"
- * (empty string) -> ""
- * " -> ""
- * @param s
- * @return
- */
- public static String quoteString(String s) {
- if (s == null) {
- return null;
- }
- if (!s.matches("^\".*\"$")) {
- return "\"" + s + "\"";
- } else {
- return s;
- }
- }
-
/**
* A fast version of URLDecoder.decode() that works only with UTF-8 and does only two
* allocations. This version is around 3x as fast as the standard one and I'm using it
diff --git a/src/com/fsck/k9/mail/Address.java b/src/com/fsck/k9/mail/Address.java
index f3810f051..38a5a0f0b 100644
--- a/src/com/fsck/k9/mail/Address.java
+++ b/src/com/fsck/k9/mail/Address.java
@@ -3,6 +3,7 @@ package com.fsck.k9.mail;
import java.util.ArrayList;
import java.util.List;
+import java.util.regex.Pattern;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.codec.EncoderUtil;
@@ -10,33 +11,15 @@ import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.dom.address.MailboxList;
import org.apache.james.mime4j.field.address.AddressBuilder;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.SpannableStringBuilder;
import android.text.TextUtils;
-import android.text.style.ForegroundColorSpan;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
-import com.fsck.k9.K9;
-import com.fsck.k9.helper.Contacts;
-import com.fsck.k9.helper.Utility;
-
+import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public class Address {
- /**
- * If the number of addresses exceeds this value the addresses aren't
- * resolved to the names of Android contacts.
- *
- *
- * TODO: This number was chosen arbitrarily and should be determined by
- * performance tests.
- *
- *
- * @see Address#toFriendly(Address[], Contacts)
- */
- private static final int TOO_MANY_ADDRESSES = 50;
+ private static final Pattern ATOM = Pattern.compile("^(?:[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]|\\s)+$");
/**
* Immutable empty {@link Address} array
@@ -162,12 +145,12 @@ public class Address {
Mailbox mailbox = (Mailbox)address;
addresses.add(new Address(mailbox.getLocalPart() + "@" + mailbox.getDomain(), mailbox.getName(), false));
} else {
- Log.e(K9.LOG_TAG, "Unknown address type from Mime4J: "
+ Log.e(LOG_TAG, "Unknown address type from Mime4J: "
+ address.getClass().toString());
}
}
} catch (MimeException pe) {
- Log.e(K9.LOG_TAG, "MimeException in Address.parse()", pe);
+ Log.e(LOG_TAG, "MimeException in Address.parse()", pe);
//but we do an silent failover : we just use the given string as name with empty address
addresses.add(new Address(null, addressList, false));
}
@@ -198,7 +181,7 @@ public class Address {
@Override
public String toString() {
if (!TextUtils.isEmpty(mPersonal)) {
- return Utility.quoteAtoms(mPersonal) + " <" + mAddress + ">";
+ return quoteAtoms(mPersonal) + " <" + mAddress + ">";
} else {
return mAddress;
}
@@ -233,77 +216,6 @@ public class Address {
return sb.toString();
}
- /**
- * Returns either the personal portion of the Address or the address portion if the personal
- * is not available.
- * @return
- */
- public CharSequence toFriendly() {
- return toFriendly((Contacts)null);
- }
-
- /**
- * Returns the name of the contact this email address belongs to if
- * the {@link Contacts contacts} parameter is not {@code null} and a
- * contact is found. Otherwise the personal portion of the {@link Address}
- * is returned. If that isn't available either, the email address is
- * returned.
- *
- * @param contacts
- * A {@link Contacts} instance or {@code null}.
- * @return
- * A "friendly" name for this {@link Address}.
- */
- public CharSequence toFriendly(final Contacts contacts) {
- if (!K9.showCorrespondentNames()) {
- return mAddress;
-
- } else if (contacts != null) {
- final String name = contacts.getNameForAddress(mAddress);
-
- // TODO: The results should probably be cached for performance reasons.
-
- if (name != null) {
- if (K9.changeContactNameColor()) {
- final SpannableString coloredName = new SpannableString(name);
- coloredName.setSpan(new ForegroundColorSpan(K9.getContactNameColor()),
- 0,
- coloredName.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
- );
- return coloredName;
- } else {
- return name;
- }
- }
- }
-
- return (!TextUtils.isEmpty(mPersonal)) ? mPersonal : mAddress;
- }
-
- public static CharSequence toFriendly(Address[] addresses) {
- return toFriendly(addresses, null);
- }
-
- public static CharSequence toFriendly(Address[] addresses, Contacts contacts) {
- if (addresses == null) {
- return null;
- }
-
- if (addresses.length >= TOO_MANY_ADDRESSES) {
- // Don't look up contacts if the number of addresses is very high.
- contacts = null;
- }
-
- SpannableStringBuilder sb = new SpannableStringBuilder();
- for (int i = 0; i < addresses.length; i++) {
- sb.append(addresses[i].toFriendly(contacts));
- if (i < addresses.length - 1) {
- sb.append(',');
- }
- }
- return sb;
- }
/**
* Unpacks an address list previously packed with packAddressList()
@@ -368,4 +280,44 @@ public class Address {
}
return sb.toString();
}
+
+ /**
+ * Quote a string, if necessary, based upon the definition of an "atom," as defined by RFC2822
+ * (http://tools.ietf.org/html/rfc2822#section-3.2.4). Strings that consist purely of atoms are
+ * left unquoted; anything else is returned as a quoted string.
+ * @param text String to quote.
+ * @return Possibly quoted string.
+ */
+ public static String quoteAtoms(final String text) {
+ if (ATOM.matcher(text).matches()) {
+ return text;
+ } else {
+ return quoteString(text);
+ }
+ }
+
+ /**
+ * Ensures that the given string starts and ends with the double quote character. The string is not modified in any way except to add the
+ * double quote character to start and end if it's not already there.
+ * sample -> "sample"
+ * "sample" -> "sample"
+ * ""sample"" -> "sample"
+ * "sample"" -> "sample"
+ * sa"mp"le -> "sa"mp"le"
+ * "sa"mp"le" -> "sa"mp"le"
+ * (empty string) -> ""
+ * " -> ""
+ * @param s
+ * @return
+ */
+ private static String quoteString(String s) {
+ if (s == null) {
+ return null;
+ }
+ if (!s.matches("^\".*\"$")) {
+ return "\"" + s + "\"";
+ } else {
+ return s;
+ }
+ }
}
diff --git a/src/com/fsck/k9/mail/Body.java b/src/com/fsck/k9/mail/Body.java
index 93960892c..d86746d66 100644
--- a/src/com/fsck/k9/mail/Body.java
+++ b/src/com/fsck/k9/mail/Body.java
@@ -5,8 +5,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import com.fsck.k9.mail.store.UnavailableStorageException;
-
public interface Body {
/**
* Returns the raw data of the body, without transfer encoding etc applied.
@@ -18,7 +16,7 @@ public interface Body {
/**
* 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 MessagingException;
/**
* Writes the body's data to the given {@link OutputStream}.
diff --git a/src/com/fsck/k9/mail/BodyPart.java b/src/com/fsck/k9/mail/BodyPart.java
index 1118304ba..551866829 100644
--- a/src/com/fsck/k9/mail/BodyPart.java
+++ b/src/com/fsck/k9/mail/BodyPart.java
@@ -1,6 +1,6 @@
-
package com.fsck.k9.mail;
+
public abstract class BodyPart implements Part {
private Multipart mParent;
@@ -13,7 +13,4 @@ public abstract class BodyPart implements Part {
}
public abstract void setEncoding(String encoding) throws MessagingException;
-
- @Override
- public abstract void setUsing7bitTransport() throws MessagingException;
}
diff --git a/src/com/fsck/k9/mail/Folder.java b/src/com/fsck/k9/mail/Folder.java
index 88af1a31a..5502ef259 100644
--- a/src/com/fsck/k9/mail/Folder.java
+++ b/src/com/fsck/k9/mail/Folder.java
@@ -1,21 +1,15 @@
package com.fsck.k9.mail;
-import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import android.util.Log;
-import com.fsck.k9.Account;
-import com.fsck.k9.K9;
-import com.fsck.k9.Preferences;
-import com.fsck.k9.controller.MessageRetrievalListener;
+import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
-public abstract class Folder {
- protected final Account mAccount;
-
+public abstract class Folder {
private String status = null;
private long lastChecked = 0;
private long lastPush = 0;
@@ -32,10 +26,6 @@ public abstract class Folder {
HOLDS_FOLDERS, HOLDS_MESSAGES,
}
- protected Folder(Account account) {
- mAccount = account;
- }
-
/**
* Forces an open of the MailProvider. If the provider is already open this
* function returns without doing anything.
@@ -83,7 +73,7 @@ public abstract class Folder {
public abstract int getUnreadMessageCount() throws MessagingException;
public abstract int getFlaggedMessageCount() throws MessagingException;
- public abstract Message getMessage(String uid) throws MessagingException;
+ public abstract T getMessage(String uid) throws MessagingException;
/**
* Fetch the shells of messages between a range of UIDs and after a given date.
@@ -94,7 +84,7 @@ public abstract class Folder {
* @return List of messages
* @throws MessagingException
*/
- public abstract List extends Message> getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) throws MessagingException;
+ public abstract List getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) throws MessagingException;
/**
* Fetches the given list of messages. The specified listener is notified as
@@ -104,13 +94,13 @@ public abstract class Folder {
* @param listener Listener to notify as we download messages.
* @return List of messages
*/
- public abstract List extends Message> getMessages(MessageRetrievalListener listener) throws MessagingException;
+ public abstract List getMessages(MessageRetrievalListener listener) throws MessagingException;
- public List extends Message> getMessages(MessageRetrievalListener listener, boolean includeDeleted) throws MessagingException {
+ public List getMessages(MessageRetrievalListener listener, boolean includeDeleted) throws MessagingException {
return getMessages(listener);
}
- public abstract List extends Message> getMessages(String[] uids, MessageRetrievalListener listener)
+ public abstract List getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException;
public abstract Map appendMessages(List extends Message> messages) throws MessagingException;
@@ -149,15 +139,15 @@ public abstract class Folder {
* @throws MessagingException
*/
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,
- MessageRetrievalListener listener) throws MessagingException {
+ MessageRetrievalListener listener) throws MessagingException {
// This is causing trouble. Disabled for now. See issue 1733
//throw new RuntimeException("fetchPart() not implemented.");
- if (K9.DEBUG)
- Log.d(K9.LOG_TAG, "fetchPart() not implemented.");
+ if (K9MailLib.isDebug())
+ Log.d(LOG_TAG, "fetchPart() not implemented.");
}
public abstract void delete(boolean recurse) throws MessagingException;
@@ -225,10 +215,6 @@ public abstract class Folder {
return getSyncClass();
}
- public void refresh(Preferences preferences) throws MessagingException {
-
- }
-
public boolean isInTopGroup() {
return false;
}
@@ -241,10 +227,6 @@ public abstract class Folder {
this.status = status;
}
- public Account getAccount() {
- return mAccount;
- }
-
public List search(String queryString, final Set requiredFlags, final Set forbiddenFlags)
throws MessagingException {
throw new MessagingException("K-9 does not support searches on this folder type");
diff --git a/src/com/fsck/k9/mail/K9MailLib.java b/src/com/fsck/k9/mail/K9MailLib.java
new file mode 100644
index 000000000..490b5e67a
--- /dev/null
+++ b/src/com/fsck/k9/mail/K9MailLib.java
@@ -0,0 +1,44 @@
+package com.fsck.k9.mail;
+
+import com.fsck.k9.K9;
+
+public class K9MailLib {
+ private K9MailLib() {}
+
+ public static final String LOG_TAG = K9.LOG_TAG;
+
+ public static final int PUSH_WAKE_LOCK_TIMEOUT = K9.PUSH_WAKE_LOCK_TIMEOUT;
+ public static final String IDENTITY_HEADER = K9.IDENTITY_HEADER;
+
+ /**
+ * Should K-9 log the conversation it has over the wire with
+ * SMTP servers?
+ */
+ public static boolean DEBUG_PROTOCOL_SMTP = true;
+
+ /**
+ * Should K-9 log the conversation it has over the wire with
+ * IMAP servers?
+ */
+ public static boolean DEBUG_PROTOCOL_IMAP = true;
+
+ /**
+ * Should K-9 log the conversation it has over the wire with
+ * POP3 servers?
+ */
+ public static boolean DEBUG_PROTOCOL_POP3 = true;
+
+ /**
+ * Should K-9 log the conversation it has over the wire with
+ * WebDAV servers?
+ */
+ public static boolean DEBUG_PROTOCOL_WEBDAV = true;
+
+ public static boolean isDebug() {
+ return K9.DEBUG;
+ }
+
+ public static boolean isDebugSensitive() {
+ return K9.DEBUG_SENSITIVE;
+ }
+}
diff --git a/src/com/fsck/k9/mail/Message.java b/src/com/fsck/k9/mail/Message.java
index 5a4d7a34a..45c85450f 100644
--- a/src/com/fsck/k9/mail/Message.java
+++ b/src/com/fsck/k9/mail/Message.java
@@ -2,26 +2,20 @@
package com.fsck.k9.mail;
import java.io.IOException;
-import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
-import java.util.HashSet;
import java.util.Set;
import android.util.Log;
-import com.fsck.k9.K9;
-import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.mail.filter.CountingOutputStream;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
-import com.fsck.k9.mail.store.UnavailableStorageException;
+
+import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public abstract class Message implements Part, CompositeBody {
-
- private MessageReference mReference = null;
-
public enum RecipientType {
TO, CC, BCC,
}
@@ -54,9 +48,8 @@ public abstract class Message implements Part, CompositeBody {
return false;
}
Message other = (Message)o;
- return (mUid.equals(other.getUid())
- && mFolder.getName().equals(other.getFolder().getName())
- && mFolder.getAccount().getUuid().equals(other.getFolder().getAccount().getUuid()));
+ return (getUid().equals(other.getUid())
+ && getFolder().getName().equals(other.getFolder().getName()));
}
@Override
@@ -65,7 +58,6 @@ public abstract class Message implements Part, CompositeBody {
int result = 1;
result = MULTIPLIER * result + mFolder.getName().hashCode();
- result = MULTIPLIER * result + mFolder.getAccount().getUuid().hashCode();
result = MULTIPLIER * result + mUid.hashCode();
return result;
}
@@ -75,7 +67,6 @@ public abstract class Message implements Part, CompositeBody {
}
public void setUid(String uid) {
- mReference = null;
this.mUid = uid;
}
@@ -97,7 +88,7 @@ public abstract class Message implements Part, CompositeBody {
public abstract Date getSentDate();
- public abstract void setSentDate(Date sentDate) throws MessagingException;
+ public abstract void setSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException;
public abstract Address[] getRecipients(RecipientType type) throws MessagingException;
@@ -126,28 +117,7 @@ public abstract class Message implements Part, CompositeBody {
public abstract void setReferences(String references) throws MessagingException;
- @Override
- public abstract Body getBody();
-
- @Override
- public abstract String getContentType() throws MessagingException;
-
- @Override
- public abstract void addHeader(String name, String value) throws MessagingException;
-
- @Override
- public abstract void setHeader(String name, String value) throws MessagingException;
-
- @Override
- public abstract String[] getHeader(String name) throws MessagingException;
-
- public abstract Set getHeaderNames() throws UnavailableStorageException;
-
- @Override
- public abstract void removeHeader(String name) throws MessagingException;
-
- @Override
- public abstract void setBody(Body body) throws MessagingException;
+ public abstract Set getHeaderNames() throws MessagingException;
public abstract long getId();
@@ -249,20 +219,10 @@ public abstract class Message implements Part, CompositeBody {
public void destroy() throws MessagingException {}
@Override
- public abstract void setEncoding(String encoding) throws UnavailableStorageException, MessagingException;
+ public abstract void setEncoding(String encoding) throws MessagingException;
public abstract void setCharset(String charset) throws MessagingException;
- public MessageReference makeMessageReference() {
- if (mReference == null) {
- mReference = new MessageReference();
- mReference.accountUuid = getFolder().getAccount().getUuid();
- mReference.folderName = getFolder().getName();
- mReference.uid = mUid;
- }
- return mReference;
- }
-
public long calculateSize() {
try {
@@ -272,9 +232,9 @@ public abstract class Message implements Part, CompositeBody {
eolOut.flush();
return out.getCount();
} catch (IOException e) {
- Log.e(K9.LOG_TAG, "Failed to calculate a message size", e);
+ Log.e(LOG_TAG, "Failed to calculate a message size", e);
} catch (MessagingException e) {
- Log.e(K9.LOG_TAG, "Failed to calculate a message size", e);
+ Log.e(LOG_TAG, "Failed to calculate a message size", e);
}
return 0;
}
@@ -282,14 +242,12 @@ public abstract class Message implements Part, CompositeBody {
/**
* Copy the contents of this object into another {@code Message} object.
*
- * @param destination
- * The {@code Message} object to receive the contents of this instance.
+ * @param destination The {@code Message} object to receive the contents of this instance.
*/
protected void copy(Message destination) {
destination.mUid = mUid;
destination.mInternalDate = mInternalDate;
destination.mFolder = mFolder;
- destination.mReference = mReference;
// mFlags contents can change during the object lifetime, so copy the Set
destination.mFlags = EnumSet.copyOf(mFlags);
@@ -308,6 +266,5 @@ public abstract class Message implements Part, CompositeBody {
*/
@Override
public abstract Message clone();
- @Override
- public abstract void setUsing7bitTransport() throws MessagingException;
+
}
diff --git a/src/com/fsck/k9/controller/MessageRetrievalListener.java b/src/com/fsck/k9/mail/MessageRetrievalListener.java
similarity index 60%
rename from src/com/fsck/k9/controller/MessageRetrievalListener.java
rename to src/com/fsck/k9/mail/MessageRetrievalListener.java
index 24ecb52e2..0d6b4fff1 100644
--- a/src/com/fsck/k9/controller/MessageRetrievalListener.java
+++ b/src/com/fsck/k9/mail/MessageRetrievalListener.java
@@ -1,12 +1,11 @@
-package com.fsck.k9.controller;
+package com.fsck.k9.mail;
-import com.fsck.k9.mail.Message;
-public interface MessageRetrievalListener {
+public interface MessageRetrievalListener {
public void messageStarted(String uid, int number, int ofTotal);
- public void messageFinished(Message message, int number, int ofTotal);
+ public void messageFinished(T message, int number, int ofTotal);
/**
* FIXME this method is almost never invoked by various Stores! Don't rely on it unless fixed!!
diff --git a/src/com/fsck/k9/mail/Multipart.java b/src/com/fsck/k9/mail/Multipart.java
index 567b0902d..8bcba92b3 100644
--- a/src/com/fsck/k9/mail/Multipart.java
+++ b/src/com/fsck/k9/mail/Multipart.java
@@ -7,7 +7,7 @@ import java.util.List;
import org.apache.james.mime4j.util.MimeUtil;
-import com.fsck.k9.mail.internet.MimeUtility;
+import com.fsck.k9.mail.internet.CharsetSupport;
import com.fsck.k9.mail.internet.TextBody;
public abstract class Multipart implements CompositeBody {
@@ -64,7 +64,7 @@ public abstract class Multipart implements CompositeBody {
BodyPart part = mParts.get(0);
Body body = part.getBody();
if (body instanceof TextBody) {
- MimeUtility.setCharset(charset, part);
+ CharsetSupport.setCharset(charset, part);
((TextBody)body).setCharset(charset);
}
}
diff --git a/src/com/fsck/k9/mail/Part.java b/src/com/fsck/k9/mail/Part.java
index 38c1ad9da..4c692b724 100644
--- a/src/com/fsck/k9/mail/Part.java
+++ b/src/com/fsck/k9/mail/Part.java
@@ -5,29 +5,29 @@ import java.io.IOException;
import java.io.OutputStream;
public interface Part {
- public void addHeader(String name, String value) throws MessagingException;
+ void addHeader(String name, String value) throws MessagingException;
- public void removeHeader(String name) throws MessagingException;
+ void removeHeader(String name) throws MessagingException;
- public void setHeader(String name, String value) throws MessagingException;
+ void setHeader(String name, String value) throws MessagingException;
- public Body getBody();
+ Body getBody();
- public String getContentType() throws MessagingException;
+ String getContentType() throws MessagingException;
- public String getDisposition() throws MessagingException;
+ String getDisposition() throws MessagingException;
- public String getContentId() throws MessagingException;
+ String getContentId() throws MessagingException;
- public String[] getHeader(String name) throws MessagingException;
+ String[] getHeader(String name) throws MessagingException;
- public boolean isMimeType(String mimeType) throws MessagingException;
+ boolean isMimeType(String mimeType) throws MessagingException;
- public String getMimeType() throws MessagingException;
+ String getMimeType() throws MessagingException;
- public void setBody(Body body) throws MessagingException;
+ void setBody(Body body) throws MessagingException;
- public void writeTo(OutputStream out) throws IOException, MessagingException;
+ void writeTo(OutputStream out) throws IOException, MessagingException;
/**
* Called just prior to transmission, once the type of transport is known to
@@ -41,5 +41,5 @@ public interface Part {
*
*/
//TODO perhaps it would be clearer to use a flag "force7bit" in writeTo
- public abstract void setUsing7bitTransport() throws MessagingException;
+ void setUsing7bitTransport() throws MessagingException;
}
diff --git a/src/com/fsck/k9/mail/ServerSettings.java b/src/com/fsck/k9/mail/ServerSettings.java
index 43e16847b..59633ffdd 100644
--- a/src/com/fsck/k9/mail/ServerSettings.java
+++ b/src/com/fsck/k9/mail/ServerSettings.java
@@ -3,7 +3,6 @@ package com.fsck.k9.mail;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import com.fsck.k9.Account;
/**
* This is an abstraction to get rid of the store- and transport-specific URIs.
@@ -13,8 +12,8 @@ import com.fsck.k9.Account;
* store/transport URIs altogether.
*
*
- * @see Account#getStoreUri()
- * @see Account#getTransportUri()
+ * @see com.fsck.k9.mail.store.StoreConfig#getStoreUri()
+ * @see com.fsck.k9.mail.store.StoreConfig#getTransportUri()
*/
public class ServerSettings {
/**
diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java
index 7dbfade7e..0892279c5 100644
--- a/src/com/fsck/k9/mail/Store.java
+++ b/src/com/fsck/k9/mail/Store.java
@@ -1,24 +1,10 @@
package com.fsck.k9.mail;
-import java.util.HashMap;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import android.app.Application;
-import android.content.Context;
-import android.util.Log;
-
-import com.fsck.k9.Account;
-import com.fsck.k9.K9;
-import com.fsck.k9.mail.store.ImapStore;
-import com.fsck.k9.mail.store.Pop3Store;
-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.WebDavStore;
/**
* Store is the access point for an email message store. It's location can be
@@ -29,191 +15,6 @@ import com.fsck.k9.mail.store.WebDavStore;
* making as few network connections as possible.
*/
public abstract class Store {
- protected static final int SOCKET_CONNECT_TIMEOUT = 30000;
- protected static final int SOCKET_READ_TIMEOUT = 60000;
-
- /**
- * Remote stores indexed by Uri.
- */
- private static Map sStores = new HashMap();
-
- /**
- * Local stores indexed by UUID because the Uri may change due to migration to/from SD-card.
- */
- private static ConcurrentMap sLocalStores = new ConcurrentHashMap();
-
- /**
- * Lock objects indexed by account UUID.
- *
- * @see #getLocalInstance(Account, Application)
- */
- private static ConcurrentMap sAccountLocks = new ConcurrentHashMap();
-
- /**
- * Get an instance of a remote mail store.
- */
- public synchronized static Store getRemoteInstance(Account account) throws MessagingException {
- String uri = account.getStoreUri();
-
- if (uri.startsWith("local")) {
- throw new RuntimeException("Asked to get non-local Store object but given LocalStore URI");
- }
-
- Store store = sStores.get(uri);
- if (store == null) {
- if (uri.startsWith("imap")) {
- store = new ImapStore(account);
- } else if (uri.startsWith("pop3")) {
- store = new Pop3Store(account);
- } else if (uri.startsWith("webdav")) {
- store = new WebDavStore(account);
- }
-
- if (store != null) {
- sStores.put(uri, store);
- }
- }
-
- if (store == null) {
- throw new MessagingException("Unable to locate an applicable Store for " + uri);
- }
-
- return store;
- }
-
- /**
- * Get an instance of a local mail store.
- *
- * @throws UnavailableStorageException
- * if not {@link StorageProvider#isReady(Context)}
- */
- public static LocalStore getLocalInstance(Account account, Application application)
- throws MessagingException {
-
- String accountUuid = account.getUuid();
-
- // Create new per-account lock object if necessary
- sAccountLocks.putIfAbsent(accountUuid, new Object());
-
- // Get the account's lock object
- Object lock = sAccountLocks.get(accountUuid);
-
- // Use per-account locks so DatabaseUpgradeService always knows which account database is
- // currently upgraded.
- synchronized (lock) {
- Store store = sLocalStores.get(accountUuid);
-
- if (store == null) {
- // Creating a LocalStore instance will create or upgrade the database if
- // necessary. This could take some time.
- store = new LocalStore(account, application);
-
- sLocalStores.put(accountUuid, store);
- }
-
- return (LocalStore) store;
- }
- }
-
- public static void removeAccount(Account account) {
- try {
- removeRemoteInstance(account);
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "Failed to reset remote store for account " + account.getUuid(), e);
- }
-
- try {
- removeLocalInstance(account);
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "Failed to reset local store for account " + account.getUuid(), e);
- }
- }
-
- /**
- * Release reference to a local mail store instance.
- *
- * @param account
- * {@link Account} instance that is used to get the local mail store instance.
- */
- private static void removeLocalInstance(Account account) {
- String accountUuid = account.getUuid();
- sLocalStores.remove(accountUuid);
- }
-
- /**
- * Release reference to a remote mail store instance.
- *
- * @param account
- * {@link Account} instance that is used to get the remote mail store instance.
- */
- private synchronized static void removeRemoteInstance(Account account) {
- String uri = account.getStoreUri();
-
- if (uri.startsWith("local")) {
- throw new RuntimeException("Asked to get non-local Store object but given " +
- "LocalStore URI");
- }
-
- sStores.remove(uri);
- }
-
- /**
- * Decodes the contents of store-specific URIs and puts them into a {@link ServerSettings}
- * object.
- *
- * @param uri
- * the store-specific URI to decode
- *
- * @return A {@link ServerSettings} object holding the settings contained in the URI.
- *
- * @see ImapStore#decodeUri(String)
- * @see Pop3Store#decodeUri(String)
- * @see WebDavStore#decodeUri(String)
- */
- public static ServerSettings decodeStoreUri(String uri) {
- if (uri.startsWith("imap")) {
- return ImapStore.decodeUri(uri);
- } else if (uri.startsWith("pop3")) {
- return Pop3Store.decodeUri(uri);
- } else if (uri.startsWith("webdav")) {
- return WebDavStore.decodeUri(uri);
- } else {
- throw new IllegalArgumentException("Not a valid store URI");
- }
- }
-
- /**
- * Creates a store URI from the information supplied in the {@link ServerSettings} object.
- *
- * @param server
- * The {@link ServerSettings} object that holds the server settings.
- *
- * @return A store URI that holds the same information as the {@code server} parameter.
- *
- * @see ImapStore#createUri(ServerSettings)
- * @see Pop3Store#createUri(ServerSettings)
- * @see WebDavStore#createUri(ServerSettings)
- */
- public static String createStoreUri(ServerSettings server) {
- if (ImapStore.STORE_TYPE.equals(server.type)) {
- return ImapStore.createUri(server);
- } else if (Pop3Store.STORE_TYPE.equals(server.type)) {
- return Pop3Store.createUri(server);
- } else if (WebDavStore.STORE_TYPE.equals(server.type)) {
- return WebDavStore.createUri(server);
- } else {
- throw new IllegalArgumentException("Not a valid store URI");
- }
- }
-
-
- protected final Account mAccount;
-
-
- protected Store(Account account) {
- mAccount = account;
- }
-
public abstract Folder getFolder(String name);
public abstract List extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException;
@@ -244,14 +45,25 @@ public abstract class Store {
return true;
}
- public void sendMessages(List extends Message> messages) throws MessagingException {
- }
+ public void sendMessages(List extends Message> messages) throws MessagingException { }
public Pusher getPusher(PushReceiver receiver) {
return null;
}
- public Account getAccount() {
- return mAccount;
+ protected static String decodeUtf8(String s) {
+ try {
+ return URLDecoder.decode(s, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 not found");
+ }
+ }
+
+ protected static String encodeUtf8(String s) {
+ try {
+ return URLEncoder.encode(s, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 not found");
+ }
}
}
diff --git a/src/com/fsck/k9/mail/Transport.java b/src/com/fsck/k9/mail/Transport.java
index 28011dacd..17d3e6866 100644
--- a/src/com/fsck/k9/mail/Transport.java
+++ b/src/com/fsck/k9/mail/Transport.java
@@ -1,22 +1,26 @@
package com.fsck.k9.mail;
-import com.fsck.k9.Account;
+import com.fsck.k9.mail.store.StoreConfig;
import com.fsck.k9.mail.transport.SmtpTransport;
import com.fsck.k9.mail.transport.WebDavTransport;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
public abstract class Transport {
protected static final int SOCKET_CONNECT_TIMEOUT = 10000;
// RFC 1047
protected static final int SOCKET_READ_TIMEOUT = 300000;
- public synchronized static Transport getInstance(Account account) throws MessagingException {
- String uri = account.getTransportUri();
+ public synchronized static Transport getInstance(StoreConfig storeConfig) throws MessagingException {
+ String uri = storeConfig.getTransportUri();
if (uri.startsWith("smtp")) {
- return new SmtpTransport(account);
+ return new SmtpTransport(storeConfig);
} else if (uri.startsWith("webdav")) {
- return new WebDavTransport(account);
+ return new WebDavTransport(storeConfig);
} else {
throw new MessagingException("Unable to locate an applicable Transport for " + uri);
}
@@ -71,4 +75,19 @@ public abstract class Transport {
public abstract void sendMessage(Message message) throws MessagingException;
public abstract void close();
+
+ protected static String encodeUtf8(String s) {
+ try {
+ return URLEncoder.encode(s, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 not found");
+ }
+ }
+ protected static String decodeUtf8(String s) {
+ try {
+ return URLDecoder.decode(s, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 not found");
+ }
+ }
}
diff --git a/src/com/fsck/k9/mail/filter/Base64.java b/src/com/fsck/k9/mail/filter/Base64.java
index 934c3b222..9f7cadfa5 100644
--- a/src/com/fsck/k9/mail/filter/Base64.java
+++ b/src/com/fsck/k9/mail/filter/Base64.java
@@ -34,6 +34,22 @@ import java.nio.charset.Charset;
* @version $Id$
*/
public class Base64 {
+ public static String decode(String encoded) {
+ if (encoded == null) {
+ return null;
+ }
+ byte[] decoded = new Base64().decode(encoded.getBytes());
+ return new String(decoded);
+ }
+
+ public static String encode(String s) {
+ if (s == null) {
+ return null;
+ }
+ byte[] encoded = new Base64().encode(s.getBytes());
+ return new String(encoded);
+ }
+
/**
* Chunk size per RFC 2045 section 6.8.
*
diff --git a/src/com/fsck/k9/mail/internet/CharsetSupport.java b/src/com/fsck/k9/mail/internet/CharsetSupport.java
new file mode 100644
index 000000000..ffd241031
--- /dev/null
+++ b/src/com/fsck/k9/mail/internet/CharsetSupport.java
@@ -0,0 +1,1121 @@
+package com.fsck.k9.mail.internet;
+
+import android.util.Log;
+
+import com.fsck.k9.mail.Message;
+import com.fsck.k9.mail.MessagingException;
+import com.fsck.k9.mail.Part;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.util.Locale;
+
+import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
+import static com.fsck.k9.mail.internet.JisSupport.SHIFT_JIS;
+
+public class CharsetSupport {
+ /**
+ * Table for character set fall-back.
+ *
+ * Table format: unsupported charset (regular expression), fall-back charset
+ */
+ private static final String[][] CHARSET_FALLBACK_MAP = new String[][] {
+ // Some Android versions don't support KOI8-U
+ {"koi8-u", "koi8-r"},
+ {"iso-2022-jp-[\\d]+", "iso-2022-jp"},
+ // Default fall-back is US-ASCII
+ {".*", "US-ASCII"}
+ };
+
+
+ public static void setCharset(String charset, Part part) throws MessagingException {
+ part.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
+ part.getMimeType() + ";\r\n charset=" + getExternalCharset(charset));
+ }
+
+
+ public static String getCharsetFromAddress(String address) {
+ String variant = JisSupport.getJisVariantFromAddress(address);
+ if (variant != null) {
+ String charset = "x-" + variant + "-shift_jis-2007";
+ if (Charset.isSupported(charset))
+ return charset;
+ }
+
+ return "UTF-8";
+ }
+
+ static String getExternalCharset(String charset) {
+ if (JisSupport.isShiftJis(charset)) {
+ return SHIFT_JIS;
+ } else {
+ return charset;
+ }
+ }
+
+ static String fixupCharset(String charset, Message message) throws MessagingException {
+ if (charset == null || "0".equals(charset))
+ charset = "US-ASCII"; // No encoding, so use us-ascii, which is the standard.
+
+ charset = charset.toLowerCase(Locale.US);
+ if (charset.equals("cp932"))
+ charset = SHIFT_JIS;
+
+ if (charset.equals(SHIFT_JIS) || charset.equals("iso-2022-jp")) {
+ String variant = JisSupport.getJisVariantFromMessage(message);
+ if (variant != null)
+ charset = "x-" + variant + "-" + charset + "-2007";
+ }
+ return charset;
+ }
+
+
+ static String readToString(InputStream in, String charset) throws IOException {
+ boolean isIphoneString = false;
+
+ // iso-2022-jp variants are supported by no versions as of Dec 2010.
+ if (charset.length() > 19 && charset.startsWith("x-") &&
+ charset.endsWith("-iso-2022-jp-2007") && !Charset.isSupported(charset)) {
+ in = new Iso2022JpToShiftJisInputStream(in);
+ charset = "x-" + charset.substring(2, charset.length() - 17) + "-shift_jis-2007";
+ }
+
+ // shift_jis variants are supported by Eclair and later.
+ if (JisSupport.isShiftJis(charset) && !Charset.isSupported(charset)) {
+ // If the JIS variant is iPhone, map the Unicode private use area in iPhone to the one in Android after
+ // converting the character set from the standard Shift JIS to Unicode.
+ if (charset.substring(2, charset.length() - 15).equals("iphone"))
+ isIphoneString = true;
+
+ charset = SHIFT_JIS;
+ }
+
+ /*
+ * See if there is conversion from the MIME charset to the Java one.
+ * this function may also throw an exception if the charset name is not known
+ */
+ boolean supported;
+ try {
+ supported = Charset.isSupported(charset);
+ } catch (IllegalCharsetNameException e) {
+ supported = false;
+ }
+
+ for (String[] rule: CHARSET_FALLBACK_MAP) {
+ if (supported) {
+ break;
+ }
+
+ if (charset.matches(rule[0])) {
+ Log.e(LOG_TAG, "I don't know how to deal with the charset " + charset +
+ ". Falling back to " + rule[1]);
+ charset = rule[1];
+ try {
+ supported = Charset.isSupported(charset);
+ } catch (IllegalCharsetNameException e) {
+ supported = false;
+ }
+ }
+ }
+
+ /*
+ * Convert and return as new String
+ */
+ String str = IOUtils.toString(in, charset);
+
+ if (isIphoneString)
+ str = importStringFromIphone(str);
+ return str;
+ }
+
+ private static String importStringFromIphone(String str) {
+ StringBuilder buff = new StringBuilder(str.length());
+ for (int i = 0; i < str.length(); i = str.offsetByCodePoints(i, 1)) {
+ int codePoint = str.codePointAt(i);
+ buff.appendCodePoint(importCodePointFromIphone(codePoint));
+ }
+ return buff.toString();
+ }
+
+ private static int importCodePointFromIphone(int codePoint) {
+ switch (codePoint) {
+ case 0xE001:
+ return 0xFE19B;
+ case 0xE002:
+ return 0xFE19C;
+ case 0xE003:
+ return 0xFE823;
+ case 0xE004:
+ return 0xFE19D;
+ case 0xE005:
+ return 0xFE19E;
+ case 0xE006:
+ return 0xFE4CF;
+ case 0xE007:
+ return 0xFE4CD;
+ case 0xE008:
+ return 0xFE4EF;
+ case 0xE009:
+ return 0xFE523;
+ case 0xE00A:
+ return 0xFE525;
+ case 0xE00B:
+ return 0xFE528;
+ case 0xE00C:
+ return 0xFE538;
+ case 0xE00D:
+ return 0xFEB96;
+ case 0xE00E:
+ return 0xFEB97;
+ case 0xE00F:
+ return 0xFEB98;
+ case 0xE010:
+ return 0xFEB93;
+ case 0xE011:
+ return 0xFEB94;
+ case 0xE012:
+ return 0xFEB95;
+ case 0xE013:
+ return 0xFE7D5;
+ case 0xE014:
+ return 0xFE7D2;
+ case 0xE015:
+ return 0xFE7D3;
+ case 0xE016:
+ return 0xFE7D1;
+ case 0xE017:
+ return 0xFE7DA;
+ case 0xE018:
+ return 0xFE7D4;
+ case 0xE019:
+ return 0xFE1BD;
+ case 0xE01A:
+ return 0xFE1BE;
+ case 0xE01B:
+ return 0xFE7E4;
+ case 0xE01C:
+ return 0xFE7EA;
+ case 0xE01D:
+ return 0xFE7E9;
+ case 0xE01E:
+ return 0xFE7DF;
+ case 0xE01F:
+ return 0xFE7E3;
+ case 0xE020:
+ return 0xFEB09;
+ case 0xE021:
+ return 0xFEB04;
+ case 0xE022:
+ return 0xFEB0C;
+ case 0xE023:
+ return 0xFEB0E;
+ case 0xE024:
+ return 0xFE01E;
+ case 0xE025:
+ return 0xFE01F;
+ case 0xE026:
+ return 0xFE020;
+ case 0xE027:
+ return 0xFE021;
+ case 0xE028:
+ return 0xFE022;
+ case 0xE029:
+ return 0xFE023;
+ case 0xE02A:
+ return 0xFE024;
+ case 0xE02B:
+ return 0xFE025;
+ case 0xE02C:
+ return 0xFE026;
+ case 0xE02D:
+ return 0xFE027;
+ case 0xE02E:
+ return 0xFE028;
+ case 0xE02F:
+ return 0xFE029;
+ case 0xE030:
+ return 0xFE040;
+ case 0xE031:
+ return 0xFE4D2;
+ case 0xE032:
+ return 0xFE041;
+ case 0xE033:
+ return 0xFE512;
+ case 0xE034:
+ return 0xFE825;
+ case 0xE035:
+ return 0xFE826;
+ case 0xE036:
+ return 0xFE4B0;
+ case 0xE037:
+ return 0xFE4BB;
+ case 0xE038:
+ return 0xFE4B2;
+ case 0xE039:
+ return 0xFE7EC;
+ case 0xE03A:
+ return 0xFE7F5;
+ case 0xE03B:
+ return 0xFE4C3;
+ case 0xE03C:
+ return 0xFE800;
+ case 0xE03D:
+ return 0xFE801;
+ case 0xE03E:
+ return 0xFE813;
+ case 0xE03F:
+ return 0xFEB82;
+ case 0xE040:
+ return 0xFE815;
+ case 0xE041:
+ return 0xFE816;
+ case 0xE042:
+ return 0xFE818;
+ case 0xE043:
+ return 0xFE980;
+ case 0xE044:
+ return 0xFE982;
+ case 0xE045:
+ return 0xFE981;
+ case 0xE046:
+ return 0xFE962;
+ case 0xE047:
+ return 0xFE983;
+ case 0xE048:
+ return 0xFE003;
+ case 0xE049:
+ return 0xFE001;
+ case 0xE04A:
+ return 0xFE000;
+ case 0xE04B:
+ return 0xFE002;
+ case 0xE04C:
+ return 0xFE014;
+ case 0xE04D:
+ return 0xFE009;
+ case 0xE04E:
+ return 0xFE1AF;
+ case 0xE04F:
+ return 0xFE1B8;
+ case 0xE050:
+ return 0xFE1C0;
+ case 0xE051:
+ return 0xFE1C1;
+ case 0xE052:
+ return 0xFE1B7;
+ case 0xE053:
+ return 0xFE1C2;
+ case 0xE054:
+ return 0xFE1C3;
+ case 0xE055:
+ return 0xFE1BC;
+ case 0xE056:
+ return 0xFE335;
+ case 0xE057:
+ return 0xFE330;
+ case 0xE058:
+ return 0xFE323;
+ case 0xE059:
+ return 0xFE320;
+ case 0xE05A:
+ return 0xFE4F4;
+ case 0xE101:
+ return 0xFE52D;
+ case 0xE102:
+ return 0xFE52E;
+ case 0xE103:
+ return 0xFE52B;
+ case 0xE104:
+ return 0xFE526;
+ case 0xE105:
+ return 0xFE329;
+ case 0xE106:
+ return 0xFE327;
+ case 0xE107:
+ return 0xFE341;
+ case 0xE108:
+ return 0xFE344;
+ case 0xE109:
+ return 0xFE1C4;
+ case 0xE10A:
+ return 0xFE1C5;
+ case 0xE10B:
+ return 0xFE1BF;
+ case 0xE10C:
+ return 0xFE1B0;
+ case 0xE10D:
+ return 0xFE7ED;
+ case 0xE10E:
+ return 0xFE4D1;
+ case 0xE10F:
+ return 0xFEB56;
+ case 0xE110:
+ return 0xFE03C;
+ case 0xE111:
+ return 0xFE827;
+ case 0xE112:
+ return 0xFE510;
+ case 0xE113:
+ return 0xFE4F5;
+ case 0xE114:
+ return 0xFEB85;
+ case 0xE115:
+ return 0xFE7D9;
+ case 0xE116:
+ return 0xFE4CA;
+ case 0xE117:
+ return 0xFE515;
+ case 0xE118:
+ return 0xFE03F;
+ case 0xE119:
+ return 0xFE042;
+ case 0xE11A:
+ return 0xFE1B2;
+ case 0xE11B:
+ return 0xFE1AE;
+ case 0xE11C:
+ return 0xFE1B3;
+ case 0xE11D:
+ return 0xFE4F6;
+ case 0xE11E:
+ return 0xFE53B;
+ case 0xE11F:
+ return 0xFE537;
+ case 0xE120:
+ return 0xFE960;
+ case 0xE121:
+ return 0xFE4BC;
+ case 0xE122:
+ return 0xFE7FB;
+ case 0xE123:
+ return 0xFE7FA;
+ case 0xE124:
+ return 0xFE7FD;
+ case 0xE125:
+ return 0xFE807;
+ case 0xE126:
+ return 0xFE81D;
+ case 0xE127:
+ return 0xFE81E;
+ case 0xE128:
+ return 0xFE81F;
+ case 0xE129:
+ return 0xFE820;
+ case 0xE12A:
+ return 0xFE81C;
+ case 0xE12B:
+ return 0xFE1B1;
+ case 0xE12C:
+ return 0xFE81B;
+ case 0xE12D:
+ return 0xFE80B;
+ case 0xE12E:
+ return 0xFEB32;
+ case 0xE12F:
+ return 0xFE4DD;
+ case 0xE130:
+ return 0xFE80C;
+ case 0xE131:
+ return 0xFE7DB;
+ case 0xE132:
+ return 0xFE7D7;
+ case 0xE133:
+ return 0xFE80D;
+ case 0xE134:
+ return 0xFE7DC;
+ case 0xE135:
+ return 0xFE7EE;
+ case 0xE136:
+ return 0xFE7EB;
+ case 0xE137:
+ return 0xFE7F8;
+ case 0xE138:
+ return 0xFEB33;
+ case 0xE139:
+ return 0xFEB34;
+ case 0xE13A:
+ return 0xFEB35;
+ case 0xE13B:
+ return 0xFE509;
+ case 0xE13C:
+ return 0xFEB59;
+ case 0xE13D:
+ return 0xFE004;
+ case 0xE13E:
+ return 0xFE4D6;
+ case 0xE13F:
+ return 0xFE505;
+ case 0xE140:
+ return 0xFE507;
+ case 0xE141:
+ return 0xFE821;
+ case 0xE142:
+ return 0xFE52F;
+ case 0xE143:
+ return 0xFE514;
+ case 0xE144:
+ return 0xFEB86;
+ case 0xE145:
+ return 0xFEB87;
+ case 0xE146:
+ return 0xFE00B;
+ case 0xE147:
+ return 0xFE965;
+ case 0xE148:
+ return 0xFE546;
+ case 0xE149:
+ return 0xFE4DE;
+ case 0xE14A:
+ return 0xFE4DF;
+ case 0xE14B:
+ return 0xFE531;
+ case 0xE14C:
+ return 0xFEB5E;
+ case 0xE14D:
+ return 0xFE4B5;
+ case 0xE14E:
+ return 0xFE7F7;
+ case 0xE14F:
+ return 0xFE7F6;
+ case 0xE150:
+ return 0xFE7E7;
+ case 0xE151:
+ return 0xFE506;
+ case 0xE152:
+ return 0xFE1A1;
+ case 0xE153:
+ return 0xFE4B3;
+ case 0xE154:
+ return 0xFE4B6;
+ case 0xE155:
+ return 0xFE4B4;
+ case 0xE156:
+ return 0xFE4B9;
+ case 0xE157:
+ return 0xFE4BA;
+ case 0xE158:
+ return 0xFE4B7;
+ case 0xE159:
+ return 0xFE7E6;
+ case 0xE15A:
+ return 0xFE7EF;
+ case 0xE201:
+ return 0xFE7F0;
+ case 0xE202:
+ return 0xFE7E8;
+ case 0xE203:
+ return 0xFEB24;
+ case 0xE204:
+ return 0xFEB19;
+ case 0xE205:
+ return 0xFEB61;
+ case 0xE206:
+ return 0xFEB62;
+ case 0xE207:
+ return 0xFEB25;
+ case 0xE208:
+ return 0xFEB1F;
+ case 0xE209:
+ return 0xFE044;
+ case 0xE20A:
+ return 0xFEB20;
+ case 0xE20B:
+ return 0xFE838;
+ case 0xE20C:
+ return 0xFEB1A;
+ case 0xE20D:
+ return 0xFEB1C;
+ case 0xE20E:
+ return 0xFEB1B;
+ case 0xE20F:
+ return 0xFEB1D;
+ case 0xE210:
+ return 0xFE82C;
+ case 0xE211:
+ return 0xFE82B;
+ case 0xE212:
+ return 0xFEB36;
+ case 0xE213:
+ return 0xFEB37;
+ case 0xE214:
+ return 0xFEB38;
+ case 0xE215:
+ return 0xFEB39;
+ case 0xE216:
+ return 0xFEB3A;
+ case 0xE217:
+ return 0xFEB3B;
+ case 0xE218:
+ return 0xFEB3C;
+ case 0xE219:
+ return 0xFEB63;
+ case 0xE21A:
+ return 0xFEB64;
+ case 0xE21B:
+ return 0xFEB67;
+ case 0xE21C:
+ return 0xFE82E;
+ case 0xE21D:
+ return 0xFE82F;
+ case 0xE21E:
+ return 0xFE830;
+ case 0xE21F:
+ return 0xFE831;
+ case 0xE220:
+ return 0xFE832;
+ case 0xE221:
+ return 0xFE833;
+ case 0xE222:
+ return 0xFE834;
+ case 0xE223:
+ return 0xFE835;
+ case 0xE224:
+ return 0xFE836;
+ case 0xE225:
+ return 0xFE837;
+ case 0xE226:
+ return 0xFEB3D;
+ case 0xE227:
+ return 0xFEB3E;
+ case 0xE228:
+ return 0xFEB3F;
+ case 0xE229:
+ return 0xFEB81;
+ case 0xE22A:
+ return 0xFEB31;
+ case 0xE22B:
+ return 0xFEB2F;
+ case 0xE22C:
+ return 0xFEB40;
+ case 0xE22D:
+ return 0xFEB41;
+ case 0xE22E:
+ return 0xFEB99;
+ case 0xE22F:
+ return 0xFEB9A;
+ case 0xE230:
+ return 0xFEB9B;
+ case 0xE231:
+ return 0xFEB9C;
+ case 0xE232:
+ return 0xFEAF8;
+ case 0xE233:
+ return 0xFEAF9;
+ case 0xE234:
+ return 0xFEAFA;
+ case 0xE235:
+ return 0xFEAFB;
+ case 0xE236:
+ return 0xFEAF0;
+ case 0xE237:
+ return 0xFEAF2;
+ case 0xE238:
+ return 0xFEAF1;
+ case 0xE239:
+ return 0xFEAF3;
+ case 0xE23A:
+ return 0xFEAFC;
+ case 0xE23B:
+ return 0xFEAFD;
+ case 0xE23C:
+ return 0xFEAFE;
+ case 0xE23D:
+ return 0xFEAFF;
+ case 0xE23E:
+ return 0xFE4F8;
+ case 0xE23F:
+ return 0xFE02B;
+ case 0xE240:
+ return 0xFE02C;
+ case 0xE241:
+ return 0xFE02D;
+ case 0xE242:
+ return 0xFE02E;
+ case 0xE243:
+ return 0xFE02F;
+ case 0xE244:
+ return 0xFE030;
+ case 0xE245:
+ return 0xFE031;
+ case 0xE246:
+ return 0xFE032;
+ case 0xE247:
+ return 0xFE033;
+ case 0xE248:
+ return 0xFE034;
+ case 0xE249:
+ return 0xFE035;
+ case 0xE24A:
+ return 0xFE036;
+ case 0xE24B:
+ return 0xFE037;
+ case 0xE24C:
+ return 0xFEB42;
+ case 0xE24D:
+ return 0xFEB27;
+ case 0xE24E:
+ return 0xFEB29;
+ case 0xE24F:
+ return 0xFEB2D;
+ case 0xE250:
+ return 0xFE839;
+ case 0xE251:
+ return 0xFE83A;
+ case 0xE252:
+ return 0xFEB23;
+ case 0xE253:
+ return 0xFE1B4;
+ case 0xE254:
+ return 0xFEE77;
+ case 0xE255:
+ return 0xFEE78;
+ case 0xE256:
+ return 0xFEE79;
+ case 0xE257:
+ return 0xFEE7A;
+ case 0xE258:
+ return 0xFEE7B;
+ case 0xE259:
+ return 0xFEE7C;
+ case 0xE25A:
+ return 0xFEE7D;
+ case 0xE301:
+ return 0xFE527;
+ case 0xE302:
+ return 0xFE4D3;
+ case 0xE303:
+ return 0xFE045;
+ case 0xE304:
+ return 0xFE03D;
+ case 0xE305:
+ return 0xFE046;
+ case 0xE306:
+ return 0xFE828;
+ case 0xE307:
+ return 0xFE047;
+ case 0xE308:
+ return 0xFE048;
+ case 0xE309:
+ return 0xFE508;
+ case 0xE30A:
+ return 0xFE803;
+ case 0xE30B:
+ return 0xFE985;
+ case 0xE30C:
+ return 0xFE987;
+ case 0xE30D:
+ return 0xFEB43;
+ case 0xE30E:
+ return 0xFEB1E;
+ case 0xE30F:
+ return 0xFE50A;
+ case 0xE310:
+ return 0xFE516;
+ case 0xE311:
+ return 0xFEB58;
+ case 0xE312:
+ return 0xFE517;
+ case 0xE313:
+ return 0xFE53E;
+ case 0xE314:
+ return 0xFE50F;
+ case 0xE315:
+ return 0xFEB2B;
+ case 0xE316:
+ return 0xFE53C;
+ case 0xE317:
+ return 0xFE530;
+ case 0xE318:
+ return 0xFE4D4;
+ case 0xE319:
+ return 0xFE4D5;
+ case 0xE31A:
+ return 0xFE4D7;
+ case 0xE31B:
+ return 0xFE4D8;
+ case 0xE31C:
+ return 0xFE195;
+ case 0xE31D:
+ return 0xFE196;
+ case 0xE31E:
+ return 0xFE197;
+ case 0xE31F:
+ return 0xFE198;
+ case 0xE320:
+ return 0xFE199;
+ case 0xE321:
+ return 0xFE4D9;
+ case 0xE322:
+ return 0xFE4DA;
+ case 0xE323:
+ return 0xFE4F0;
+ case 0xE324:
+ return 0xFE808;
+ case 0xE325:
+ return 0xFE4F2;
+ case 0xE326:
+ return 0xFE814;
+ case 0xE327:
+ return 0xFEB0D;
+ case 0xE328:
+ return 0xFEB11;
+ case 0xE329:
+ return 0xFEB12;
+ case 0xE32A:
+ return 0xFEB13;
+ case 0xE32B:
+ return 0xFEB14;
+ case 0xE32C:
+ return 0xFEB15;
+ case 0xE32D:
+ return 0xFEB16;
+ case 0xE32E:
+ return 0xFEB60;
+ case 0xE32F:
+ return 0xFEB68;
+ case 0xE330:
+ return 0xFEB5D;
+ case 0xE331:
+ return 0xFEB5B;
+ case 0xE332:
+ return 0xFEB44;
+ case 0xE333:
+ return 0xFEB45;
+ case 0xE334:
+ return 0xFEB57;
+ case 0xE335:
+ return 0xFEB69;
+ case 0xE336:
+ return 0xFEB0A;
+ case 0xE337:
+ return 0xFEB0B;
+ case 0xE338:
+ return 0xFE984;
+ case 0xE339:
+ return 0xFE964;
+ case 0xE33A:
+ return 0xFE966;
+ case 0xE33B:
+ return 0xFE967;
+ case 0xE33C:
+ return 0xFE968;
+ case 0xE33D:
+ return 0xFE969;
+ case 0xE33E:
+ return 0xFE96A;
+ case 0xE33F:
+ return 0xFE96B;
+ case 0xE340:
+ return 0xFE963;
+ case 0xE341:
+ return 0xFE96C;
+ case 0xE342:
+ return 0xFE961;
+ case 0xE343:
+ return 0xFE96D;
+ case 0xE344:
+ return 0xFE96E;
+ case 0xE345:
+ return 0xFE051;
+ case 0xE346:
+ return 0xFE052;
+ case 0xE347:
+ return 0xFE053;
+ case 0xE348:
+ return 0xFE054;
+ case 0xE349:
+ return 0xFE055;
+ case 0xE34A:
+ return 0xFE056;
+ case 0xE34B:
+ return 0xFE511;
+ case 0xE34C:
+ return 0xFE96F;
+ case 0xE34D:
+ return 0xFE970;
+ case 0xE401:
+ return 0xFE345;
+ case 0xE402:
+ return 0xFE343;
+ case 0xE403:
+ return 0xFE340;
+ case 0xE404:
+ return 0xFE333;
+ case 0xE405:
+ return 0xFE347;
+ case 0xE406:
+ return 0xFE33C;
+ case 0xE407:
+ return 0xFE33F;
+ case 0xE408:
+ return 0xFE342;
+ case 0xE409:
+ return 0xFE32A;
+ case 0xE40A:
+ return 0xFE33E;
+ case 0xE40B:
+ return 0xFE33B;
+ case 0xE40C:
+ return 0xFE32E;
+ case 0xE40D:
+ return 0xFE32F;
+ case 0xE40E:
+ return 0xFE326;
+ case 0xE40F:
+ return 0xFE325;
+ case 0xE410:
+ return 0xFE322;
+ case 0xE411:
+ return 0xFE33A;
+ case 0xE412:
+ return 0xFE334;
+ case 0xE413:
+ return 0xFE339;
+ case 0xE414:
+ return 0xFE336;
+ case 0xE415:
+ return 0xFE338;
+ case 0xE416:
+ return 0xFE33D;
+ case 0xE417:
+ return 0xFE32D;
+ case 0xE418:
+ return 0xFE32C;
+ case 0xE419:
+ return 0xFE190;
+ case 0xE41A:
+ return 0xFE192;
+ case 0xE41B:
+ return 0xFE191;
+ case 0xE41C:
+ return 0xFE193;
+ case 0xE41D:
+ return 0xFE35B;
+ case 0xE41E:
+ return 0xFEB9D;
+ case 0xE41F:
+ return 0xFEB9E;
+ case 0xE420:
+ return 0xFEB9F;
+ case 0xE421:
+ return 0xFEBA0;
+ case 0xE422:
+ return 0xFEBA1;
+ case 0xE423:
+ return 0xFE351;
+ case 0xE424:
+ return 0xFE352;
+ case 0xE425:
+ return 0xFE829;
+ case 0xE426:
+ return 0xFE353;
+ case 0xE427:
+ return 0xFE358;
+ case 0xE428:
+ return 0xFE1A0;
+ case 0xE429:
+ return 0xFE1A2;
+ case 0xE42A:
+ return 0xFE7D6;
+ case 0xE42B:
+ return 0xFE7DD;
+ case 0xE42C:
+ return 0xFE80E;
+ case 0xE42D:
+ return 0xFE7DE;
+ case 0xE42E:
+ return 0xFE7E5;
+ case 0xE42F:
+ return 0xFE7F1;
+ case 0xE430:
+ return 0xFE7F2;
+ case 0xE431:
+ return 0xFE7F3;
+ case 0xE432:
+ return 0xFE7F4;
+ case 0xE433:
+ return 0xFE7FE;
+ case 0xE434:
+ return 0xFE7E0;
+ case 0xE435:
+ return 0xFE7E2;
+ case 0xE436:
+ return 0xFE518;
+ case 0xE437:
+ return 0xFEB17;
+ case 0xE438:
+ return 0xFE519;
+ case 0xE439:
+ return 0xFE51A;
+ case 0xE43A:
+ return 0xFE51B;
+ case 0xE43B:
+ return 0xFE51C;
+ case 0xE43C:
+ return 0xFE007;
+ case 0xE43D:
+ return 0xFE82A;
+ case 0xE43E:
+ return 0xFE038;
+ case 0xE43F:
+ return 0xFE971;
+ case 0xE440:
+ return 0xFE51D;
+ case 0xE441:
+ return 0xFE1C6;
+ case 0xE442:
+ return 0xFE51E;
+ case 0xE443:
+ return 0xFE005;
+ case 0xE444:
+ return 0xFE049;
+ case 0xE445:
+ return 0xFE51F;
+ case 0xE446:
+ return 0xFE017;
+ case 0xE447:
+ return 0xFE043;
+ case 0xE448:
+ return 0xFE513;
+ case 0xE449:
+ return 0xFE00A;
+ case 0xE44A:
+ return 0xFE00C;
+ case 0xE44B:
+ return 0xFE008;
+ case 0xE44C:
+ return 0xFE00D;
+ case 0xE501:
+ return 0xFE4B8;
+ case 0xE502:
+ return 0xFE804;
+ case 0xE503:
+ return 0xFE805;
+ case 0xE504:
+ return 0xFE4BD;
+ case 0xE505:
+ return 0xFE4BE;
+ case 0xE506:
+ return 0xFE4BF;
+ case 0xE507:
+ return 0xFE802;
+ case 0xE508:
+ return 0xFE4C0;
+ case 0xE509:
+ return 0xFE4C4;
+ case 0xE50A:
+ return 0xFE4C5;
+ case 0xE50B:
+ return 0xFE4E5;
+ case 0xE50C:
+ return 0xFE4E6;
+ case 0xE50D:
+ return 0xFE4E7;
+ case 0xE50E:
+ return 0xFE4E8;
+ case 0xE50F:
+ return 0xFE4E9;
+ case 0xE510:
+ return 0xFE4EA;
+ case 0xE511:
+ return 0xFE4EB;
+ case 0xE512:
+ return 0xFE4EC;
+ case 0xE513:
+ return 0xFE4ED;
+ case 0xE514:
+ return 0xFE4EE;
+ case 0xE515:
+ return 0xFE1A4;
+ case 0xE516:
+ return 0xFE1A5;
+ case 0xE517:
+ return 0xFE1A6;
+ case 0xE518:
+ return 0xFE1A7;
+ case 0xE519:
+ return 0xFE1A8;
+ case 0xE51A:
+ return 0xFE1A9;
+ case 0xE51B:
+ return 0xFE1AA;
+ case 0xE51C:
+ return 0xFE1AB;
+ case 0xE51D:
+ return 0xFE4C6;
+ case 0xE51E:
+ return 0xFE1B5;
+ case 0xE51F:
+ return 0xFE1B6;
+ case 0xE520:
+ return 0xFE1C7;
+ case 0xE521:
+ return 0xFE1C8;
+ case 0xE522:
+ return 0xFE1C9;
+ case 0xE523:
+ return 0xFE1BA;
+ case 0xE524:
+ return 0xFE1CA;
+ case 0xE525:
+ return 0xFE1CB;
+ case 0xE526:
+ return 0xFE1CC;
+ case 0xE527:
+ return 0xFE1CD;
+ case 0xE528:
+ return 0xFE1CE;
+ case 0xE529:
+ return 0xFE1CF;
+ case 0xE52A:
+ return 0xFE1D0;
+ case 0xE52B:
+ return 0xFE1D1;
+ case 0xE52C:
+ return 0xFE1D2;
+ case 0xE52D:
+ return 0xFE1D3;
+ case 0xE52E:
+ return 0xFE1D4;
+ case 0xE52F:
+ return 0xFE1D5;
+ case 0xE530:
+ return 0xFE1D6;
+ case 0xE531:
+ return 0xFE1D7;
+ case 0xE532:
+ return 0xFE50B;
+ case 0xE533:
+ return 0xFE50C;
+ case 0xE534:
+ return 0xFE50D;
+ case 0xE535:
+ return 0xFE50E;
+ case 0xE536:
+ return 0xFE553;
+ case 0xE537:
+ return 0xFEB2A;
+ case 0xE538:
+ return 0xFEE70;
+ case 0xE539:
+ return 0xFEE71;
+ case 0xE53A:
+ return 0xFEE72;
+ case 0xE53B:
+ return 0xFEE73;
+ case 0xE53C:
+ return 0xFEE74;
+ case 0xE53D:
+ return 0xFEE75;
+ case 0xE53E:
+ return 0xFEE76;
+ default:
+ return codePoint;
+ }
+ }
+
+}
diff --git a/src/com/fsck/k9/mail/internet/DecoderUtil.java b/src/com/fsck/k9/mail/internet/DecoderUtil.java
index 3ac74947d..d17c83bc7 100644
--- a/src/com/fsck/k9/mail/internet/DecoderUtil.java
+++ b/src/com/fsck/k9/mail/internet/DecoderUtil.java
@@ -2,7 +2,6 @@
package com.fsck.k9.mail.internet;
import android.util.Log;
-import com.fsck.k9.K9;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import java.io.ByteArrayInputStream;
@@ -13,6 +12,8 @@ import org.apache.james.mime4j.codec.Base64InputStream;
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
import org.apache.james.mime4j.util.CharsetUtil;
+import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
+
/**
* Static methods for decoding strings, byte arrays and encoded words.
@@ -35,7 +36,7 @@ class DecoderUtil {
Base64InputStream is = new Base64InputStream(new ByteArrayInputStream(bytes));
try {
- return MimeUtility.readToString(is, charset);
+ return CharsetSupport.readToString(is, charset);
} catch (IOException e) {
return null;
}
@@ -68,7 +69,7 @@ class DecoderUtil {
QuotedPrintableInputStream is = new QuotedPrintableInputStream(new ByteArrayInputStream(bytes));
try {
- return MimeUtility.readToString(is, charset);
+ return CharsetSupport.readToString(is, charset);
} catch (IOException e) {
return null;
}
@@ -162,13 +163,13 @@ class DecoderUtil {
String charset;
try {
- charset = MimeUtility.fixupCharset(mimeCharset, message);
+ charset = CharsetSupport.fixupCharset(mimeCharset, message);
} catch (MessagingException e) {
return null;
}
if (encodedText.isEmpty()) {
- Log.w(K9.LOG_TAG, "Missing encoded text in encoded word: '" + body.substring(begin, end) + "'");
+ Log.w(LOG_TAG, "Missing encoded text in encoded word: '" + body.substring(begin, end) + "'");
return null;
}
@@ -177,7 +178,7 @@ class DecoderUtil {
} else if (encoding.equalsIgnoreCase("B")) {
return DecoderUtil.decodeB(encodedText, charset);
} else {
- Log.w(K9.LOG_TAG, "Warning: Unknown encoding in encoded word '" + body.substring(begin, end) + "'");
+ Log.w(LOG_TAG, "Warning: Unknown encoding in encoded word '" + body.substring(begin, end) + "'");
return null;
}
}
diff --git a/src/com/fsck/k9/mail/internet/EncoderUtil.java b/src/com/fsck/k9/mail/internet/EncoderUtil.java
index 5e7672715..2c71ecc2d 100644
--- a/src/com/fsck/k9/mail/internet/EncoderUtil.java
+++ b/src/com/fsck/k9/mail/internet/EncoderUtil.java
@@ -68,7 +68,7 @@ class EncoderUtil {
if (charset == null)
charset = determineCharset(text);
- String mimeCharset = MimeUtility.getExternalCharset(charset.name());
+ String mimeCharset = CharsetSupport.getExternalCharset(charset.name());
byte[] bytes = encode(text, charset);
diff --git a/src/com/fsck/k9/mail/internet/JisSupport.java b/src/com/fsck/k9/mail/internet/JisSupport.java
new file mode 100644
index 000000000..a6a30329a
--- /dev/null
+++ b/src/com/fsck/k9/mail/internet/JisSupport.java
@@ -0,0 +1,103 @@
+package com.fsck.k9.mail.internet;
+
+import com.fsck.k9.mail.Address;
+import com.fsck.k9.mail.Message;
+import com.fsck.k9.mail.MessagingException;
+import com.fsck.k9.mail.Part;
+
+class JisSupport {
+ public static final String SHIFT_JIS = "shift_jis";
+
+ public static String getJisVariantFromMessage(Message message) throws MessagingException {
+ if (message == null)
+ return null;
+
+ // If a receiver is known to use a JIS variant, the sender transfers the message after converting the
+ // charset as a convention.
+ String variant = getJisVariantFromReceivedHeaders(message);
+ if (variant != null)
+ return variant;
+
+ // If a receiver is not known to use any JIS variants, the sender transfers the message without converting
+ // the charset.
+ variant = getJisVariantFromFromHeaders(message);
+ if (variant != null)
+ return variant;
+
+ return getJisVariantFromMailerHeaders(message);
+ }
+
+ public static boolean isShiftJis(String charset) {
+ return charset.length() > 17 && charset.startsWith("x-")
+ && charset.endsWith("-shift_jis-2007");
+ }
+
+ public static String getJisVariantFromAddress(String address) {
+ if (address == null)
+ return null;
+ if (isInDomain(address, "docomo.ne.jp") || isInDomain(address, "dwmail.jp") ||
+ isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com") ||
+ isInDomain(address, "emnet.ne.jp") || isInDomain(address, "emobile.ne.jp"))
+ return "docomo";
+ else if (isInDomain(address, "softbank.ne.jp") || isInDomain(address, "vodafone.ne.jp") ||
+ isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp"))
+ return "softbank";
+ else if (isInDomain(address, "ezweb.ne.jp") || isInDomain(address, "ido.ne.jp"))
+ return "kddi";
+ return null;
+ }
+
+
+ private static String getJisVariantFromMailerHeaders(Message message) throws MessagingException {
+ String mailerHeaders[] = message.getHeader("X-Mailer");
+ if (mailerHeaders == null || mailerHeaders.length == 0)
+ return null;
+
+ if (mailerHeaders[0].startsWith("iPhone Mail ") || mailerHeaders[0].startsWith("iPad Mail "))
+ return "iphone";
+
+ return null;
+ }
+
+
+ private static String getJisVariantFromReceivedHeaders(Part message) throws MessagingException {
+ String receivedHeaders[] = message.getHeader("Received");
+ if (receivedHeaders == null)
+ return null;
+
+ for (String receivedHeader : receivedHeaders) {
+ String address = getAddressFromReceivedHeader(receivedHeader);
+ if (address == null)
+ continue;
+ String variant = getJisVariantFromAddress(address);
+ if (variant != null)
+ return variant;
+ }
+ return null;
+ }
+
+ private static String getAddressFromReceivedHeader(String receivedHeader) {
+ // Not implemented yet! Extract an address from the FOR clause of the given Received header.
+ return null;
+ }
+
+ private static String getJisVariantFromFromHeaders(Message message) throws MessagingException {
+ Address addresses[] = message.getFrom();
+ if (addresses == null || addresses.length == 0)
+ return null;
+
+ return getJisVariantFromAddress(addresses[0].getAddress());
+ }
+
+ private static boolean isInDomain(String address, String domain) {
+ int index = address.length() - domain.length() - 1;
+ if (index < 0)
+ return false;
+
+ char c = address.charAt(index);
+ if (c != '@' && c != '.')
+ return false;
+
+ return address.endsWith(domain);
+ }
+}
diff --git a/src/com/fsck/k9/mail/internet/MessageExtractor.java b/src/com/fsck/k9/mail/internet/MessageExtractor.java
new file mode 100644
index 000000000..ba0bfa42f
--- /dev/null
+++ b/src/com/fsck/k9/mail/internet/MessageExtractor.java
@@ -0,0 +1,453 @@
+package com.fsck.k9.mail.internet;
+
+import android.util.Log;
+
+import com.fsck.k9.mail.Body;
+import com.fsck.k9.mail.BodyPart;
+import com.fsck.k9.mail.Message;
+import com.fsck.k9.mail.MessagingException;
+import com.fsck.k9.mail.Multipart;
+import com.fsck.k9.mail.Part;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
+import static com.fsck.k9.mail.internet.CharsetSupport.fixupCharset;
+import static com.fsck.k9.mail.internet.MimeUtility.getHeaderParameter;
+import static com.fsck.k9.mail.internet.Viewable.Alternative;
+import static com.fsck.k9.mail.internet.Viewable.Html;
+import static com.fsck.k9.mail.internet.Viewable.MessageHeader;
+import static com.fsck.k9.mail.internet.Viewable.Text;
+import static com.fsck.k9.mail.internet.Viewable.Textual;
+
+public class MessageExtractor {
+ private MessageExtractor() {}
+
+ public static String getTextFromPart(Part part) {
+ try {
+ if ((part != null) && (part.getBody() != null)) {
+ final Body body = part.getBody();
+ if (body instanceof TextBody) {
+ return ((TextBody)body).getText();
+ }
+
+ final String mimeType = part.getMimeType();
+ if ((mimeType != null) && MimeUtility.mimeTypeMatches(mimeType, "text/*")) {
+ /*
+ * We've got a text part, so let's see if it needs to be processed further.
+ */
+ String charset = getHeaderParameter(part.getContentType(), "charset");
+ /*
+ * determine the charset from HTML message.
+ */
+ if (mimeType.equalsIgnoreCase("text/html") && charset == null) {
+ InputStream in = part.getBody().getInputStream();
+ try {
+ byte[] buf = new byte[256];
+ in.read(buf, 0, buf.length);
+ String str = new String(buf, "US-ASCII");
+
+ if (str.isEmpty()) {
+ return "";
+ }
+ Pattern p = Pattern.compile("", Pattern.CASE_INSENSITIVE);
+ Matcher m = p.matcher(str);
+ if (m.find()) {
+ charset = m.group(1);
+ }
+ } finally {
+ try {
+ if (in instanceof BinaryTempFileBody.BinaryTempFileBodyInputStream) {
+ /*
+ * If this is a BinaryTempFileBodyInputStream, calling close()
+ * will delete the file. But we can't let that happen because
+ * the file needs to be opened again by the code a few lines
+ * down.
+ */
+ ((BinaryTempFileBody.BinaryTempFileBodyInputStream) in).closeWithoutDeleting();
+ } else {
+ in.close();
+ }
+ } catch (Exception e) { /* ignore */ }
+ }
+ }
+ charset = fixupCharset(charset, getMessageFromPart(part));
+
+ /*
+ * Now we read the part into a buffer for further processing. Because
+ * the stream is now wrapped we'll remove any transfer encoding at this point.
+ */
+ InputStream in = part.getBody().getInputStream();
+ try {
+ String text = CharsetSupport.readToString(in, charset);
+
+ // Replace the body with a TextBody that already contains the decoded text
+ part.setBody(new TextBody(text));
+
+ return text;
+ } finally {
+ try {
+ /*
+ * This time we don't care if it's a BinaryTempFileBodyInputStream. We
+ * replaced the body with a TextBody instance and hence don't need the
+ * file anymore.
+ */
+ in.close();
+ } catch (IOException e) { /* Ignore */ }
+ }
+ }
+ }
+
+ } catch (OutOfMemoryError oom) {
+ /*
+ * If we are not able to process the body there's nothing we can do about it. Return
+ * null and let the upper layers handle the missing content.
+ */
+ Log.e(LOG_TAG, "Unable to getTextFromPart " + oom.toString());
+ } catch (Exception e) {
+ /*
+ * If we are not able to process the body there's nothing we can do about it. Return
+ * null and let the upper layers handle the missing content.
+ */
+ Log.e(LOG_TAG, "Unable to getTextFromPart", e);
+ }
+ return null;
+ }
+
+
+ /**
+ * Traverse the MIME tree of a message an extract viewable parts.
+ *
+ * @param part
+ * The message part to start from.
+ * @param attachments
+ * A list that will receive the parts that are considered attachments.
+ *
+ * @return A list of {@link Viewable}s.
+ *
+ * @throws MessagingException
+ * In case of an error.
+ */
+ public static List getViewables(Part part, List attachments) throws MessagingException {
+ List viewables = new ArrayList();
+
+ Body body = part.getBody();
+ if (body instanceof Multipart) {
+ Multipart multipart = (Multipart) body;
+ if (part.getMimeType().equalsIgnoreCase("multipart/alternative")) {
+ /*
+ * For multipart/alternative parts we try to find a text/plain and a text/html
+ * child. Everything else we find is put into 'attachments'.
+ */
+ List text = findTextPart(multipart, true);
+
+ Set knownTextParts = getParts(text);
+ List html = findHtmlPart(multipart, knownTextParts, attachments, true);
+
+ if (!text.isEmpty() || !html.isEmpty()) {
+ Alternative alternative = new Alternative(text, html);
+ viewables.add(alternative);
+ }
+ } else {
+ // For all other multipart parts we recurse to grab all viewable children.
+ for (Part bodyPart : multipart.getBodyParts()) {
+ viewables.addAll(getViewables(bodyPart, attachments));
+ }
+ }
+ } else if (body instanceof Message &&
+ !("attachment".equalsIgnoreCase(getContentDisposition(part)))) {
+ /*
+ * We only care about message/rfc822 parts whose Content-Disposition header has a value
+ * other than "attachment".
+ */
+ Message message = (Message) body;
+
+ // We add the Message object so we can extract the filename later.
+ viewables.add(new MessageHeader(part, message));
+
+ // Recurse to grab all viewable parts and attachments from that message.
+ viewables.addAll(getViewables(message, attachments));
+ } else if (isPartTextualBody(part)) {
+ /*
+ * Save text/plain and text/html
+ */
+ String mimeType = part.getMimeType();
+ if (mimeType.equalsIgnoreCase("text/plain")) {
+ Text text = new Text(part);
+ viewables.add(text);
+ } else {
+ Html html = new Html(part);
+ viewables.add(html);
+ }
+ } else {
+ // Everything else is treated as attachment.
+ attachments.add(part);
+ }
+
+ return viewables;
+ }
+
+ public static Set getTextParts(Part part) throws MessagingException {
+ List attachments = new ArrayList();
+ return getParts(getViewables(part, attachments));
+ }
+
+ /**
+ * Collect attachment parts of a message.
+ * @return A list of parts regarded as attachments.
+ * @throws MessagingException In case of an error.
+ */
+ public static List collectAttachments(Message message) throws MessagingException {
+ try {
+ List attachments = new ArrayList();
+ getViewables(message, attachments);
+ return attachments;
+ } catch (Exception e) {
+ throw new MessagingException("Couldn't collect attachment parts", e);
+ }
+ }
+
+ /**
+ * Collect the viewable textual parts of a message.
+ * @return A set of viewable parts of the message.
+ * @throws MessagingException In case of an error.
+ */
+ public static Set collectTextParts(Message message) throws MessagingException {
+ try {
+ return getTextParts(message);
+ } catch (Exception e) {
+ throw new MessagingException("Couldn't extract viewable parts", e);
+ }
+ }
+
+ private static Message getMessageFromPart(Part part) {
+ while (part != null) {
+ if (part instanceof Message)
+ return (Message)part;
+
+ if (!(part instanceof BodyPart))
+ return null;
+
+ Multipart multipart = ((BodyPart)part).getParent();
+ if (multipart == null)
+ return null;
+
+ part = multipart.getParent();
+ }
+ return null;
+ }
+
+ /**
+ * Search the children of a {@link Multipart} for {@code text/plain} parts.
+ *
+ * @param multipart The {@code Multipart} to search through.
+ * @param directChild If {@code true}, this method will return after the first {@code text/plain} was
+ * found.
+ *
+ * @return A list of {@link Text} viewables.
+ *
+ * @throws MessagingException
+ * In case of an error.
+ */
+ private static List findTextPart(Multipart multipart, boolean directChild)
+ throws MessagingException {
+ List viewables = new ArrayList();
+
+ for (Part part : multipart.getBodyParts()) {
+ Body body = part.getBody();
+ if (body instanceof Multipart) {
+ Multipart innerMultipart = (Multipart) body;
+
+ /*
+ * Recurse to find text parts. Since this is a multipart that is a child of a
+ * multipart/alternative we don't want to stop after the first text/plain part
+ * we find. This will allow to get all text parts for constructions like this:
+ *
+ * 1. multipart/alternative
+ * 1.1. multipart/mixed
+ * 1.1.1. text/plain
+ * 1.1.2. text/plain
+ * 1.2. text/html
+ */
+ List textViewables = findTextPart(innerMultipart, false);
+
+ if (!textViewables.isEmpty()) {
+ viewables.addAll(textViewables);
+ if (directChild) {
+ break;
+ }
+ }
+ } else if (isPartTextualBody(part) && part.getMimeType().equalsIgnoreCase("text/plain")) {
+ Text text = new Text(part);
+ viewables.add(text);
+ if (directChild) {
+ break;
+ }
+ }
+ }
+ return viewables;
+ }
+
+ /**
+ * Search the children of a {@link Multipart} for {@code text/html} parts.
+ * Every part that is not a {@code text/html} we want to display, we add to 'attachments'.
+ *
+ * @param multipart The {@code Multipart} to search through.
+ * @param knownTextParts A set of {@code text/plain} parts that shouldn't be added to 'attachments'.
+ * @param attachments A list that will receive the parts that are considered attachments.
+ * @param directChild If {@code true}, this method will add all {@code text/html} parts except the first
+ * found to 'attachments'.
+ *
+ * @return A list of {@link Text} viewables.
+ *
+ * @throws MessagingException In case of an error.
+ */
+ private static List findHtmlPart(Multipart multipart, Set knownTextParts,
+ List attachments, boolean directChild) throws MessagingException {
+ List viewables = new ArrayList();
+
+ boolean partFound = false;
+ for (Part part : multipart.getBodyParts()) {
+ Body body = part.getBody();
+ if (body instanceof Multipart) {
+ Multipart innerMultipart = (Multipart) body;
+
+ if (directChild && partFound) {
+ // We already found our text/html part. Now we're only looking for attachments.
+ findAttachments(innerMultipart, knownTextParts, attachments);
+ } else {
+ /*
+ * Recurse to find HTML parts. Since this is a multipart that is a child of a
+ * multipart/alternative we don't want to stop after the first text/html part
+ * we find. This will allow to get all text parts for constructions like this:
+ *
+ * 1. multipart/alternative
+ * 1.1. text/plain
+ * 1.2. multipart/mixed
+ * 1.2.1. text/html
+ * 1.2.2. text/html
+ * 1.3. image/jpeg
+ */
+ List htmlViewables = findHtmlPart(innerMultipart, knownTextParts,
+ attachments, false);
+
+ if (!htmlViewables.isEmpty()) {
+ partFound = true;
+ viewables.addAll(htmlViewables);
+ }
+ }
+ } else if (!(directChild && partFound) && isPartTextualBody(part) &&
+ part.getMimeType().equalsIgnoreCase("text/html")) {
+ Html html = new Html(part);
+ viewables.add(html);
+ partFound = true;
+ } else if (!knownTextParts.contains(part)) {
+ // Only add this part as attachment if it's not a viewable text/plain part found
+ // earlier.
+ attachments.add(part);
+ }
+ }
+
+ return viewables;
+ }
+
+ /**
+ * Traverse the MIME tree and add everything that's not a known text part to 'attachments'.
+ *
+ * @param multipart
+ * The {@link Multipart} to start from.
+ * @param knownTextParts
+ * A set of known text parts we don't want to end up in 'attachments'.
+ * @param attachments
+ * A list that will receive the parts that are considered attachments.
+ */
+ private static void findAttachments(Multipart multipart, Set knownTextParts,
+ List attachments) {
+ for (Part part : multipart.getBodyParts()) {
+ Body body = part.getBody();
+ if (body instanceof Multipart) {
+ Multipart innerMultipart = (Multipart) body;
+ findAttachments(innerMultipart, knownTextParts, attachments);
+ } else if (!knownTextParts.contains(part)) {
+ attachments.add(part);
+ }
+ }
+ }
+
+ /**
+ * Build a set of message parts for fast lookups.
+ *
+ * @param viewables
+ * A list of {@link Viewable}s containing references to the message parts to include in
+ * the set.
+ *
+ * @return The set of viewable {@code Part}s.
+ *
+ * @see MessageExtractor#findHtmlPart(Multipart, Set, List, boolean)
+ * @see MessageExtractor#findAttachments(Multipart, Set, List)
+ */
+ private static Set getParts(List viewables) {
+ Set parts = new HashSet();
+
+ for (Viewable viewable : viewables) {
+ if (viewable instanceof Textual) {
+ parts.add(((Textual) viewable).getPart());
+ } else if (viewable instanceof Alternative) {
+ Alternative alternative = (Alternative) viewable;
+ parts.addAll(getParts(alternative.getText()));
+ parts.addAll(getParts(alternative.getHtml()));
+ }
+ }
+
+ return parts;
+ }
+
+ private static Boolean isPartTextualBody(Part part) throws MessagingException {
+ String disposition = part.getDisposition();
+ String dispositionType = null;
+ String dispositionFilename = null;
+ if (disposition != null) {
+ dispositionType = MimeUtility.getHeaderParameter(disposition, null);
+ dispositionFilename = MimeUtility.getHeaderParameter(disposition, "filename");
+ }
+
+ /*
+ * A best guess that this part is intended to be an attachment and not inline.
+ */
+ boolean attachment = ("attachment".equalsIgnoreCase(dispositionType) || (dispositionFilename != null));
+
+ if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/html"))) {
+ return true;
+ }
+ /*
+ * If the part is plain text and it got this far it's part of a
+ * mixed (et al) and should be rendered inline.
+ */
+ else if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/plain"))) {
+ return true;
+ }
+ /*
+ * Finally, if it's nothing else we will include it as an attachment.
+ */
+ else {
+ return false;
+ }
+ }
+
+ private static String getContentDisposition(Part part) {
+ try {
+ String disposition = part.getDisposition();
+ if (disposition != null) {
+ return MimeUtility.getHeaderParameter(disposition, null);
+ }
+ } catch (MessagingException e) { /* ignore */ }
+ return null;
+ }
+}
diff --git a/src/com/fsck/k9/mail/internet/MimeHeader.java b/src/com/fsck/k9/mail/internet/MimeHeader.java
index 12bd5fd31..7c9569df0 100644
--- a/src/com/fsck/k9/mail/internet/MimeHeader.java
+++ b/src/com/fsck/k9/mail/internet/MimeHeader.java
@@ -1,8 +1,6 @@
package com.fsck.k9.mail.internet;
-import com.fsck.k9.helper.Utility;
-
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
@@ -97,7 +95,7 @@ public class MimeHeader {
public void writeTo(OutputStream out) throws IOException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
for (Field field : mFields) {
- if (!Utility.arrayContains(writeOmitFields, field.name)) {
+ if (!Arrays.asList(writeOmitFields).contains(field.name)) {
String v = field.value;
if (hasToBeEncoded(v)) {
diff --git a/src/com/fsck/k9/mail/internet/MimeMessage.java b/src/com/fsck/k9/mail/internet/MimeMessage.java
index 75fa217d8..eff4ffa2e 100644
--- a/src/com/fsck/k9/mail/internet/MimeMessage.java
+++ b/src/com/fsck/k9/mail/internet/MimeMessage.java
@@ -33,8 +33,6 @@ import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
-import com.fsck.k9.mail.store.UnavailableStorageException;
-import com.fsck.k9.K9;
/**
* An implementation of Message that stores all of it's metadata in RFC 822 and
@@ -138,12 +136,12 @@ public class MimeMessage extends Message {
* @param sentDate
* @throws com.fsck.k9.mail.MessagingException
*/
- public void addSentDate(Date sentDate) throws MessagingException {
+ public void addSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException {
if (mDateFormat == null) {
mDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
}
- if (K9.hideTimeZone()) {
+ if (hideTimeZone) {
mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@@ -152,9 +150,9 @@ public class MimeMessage extends Message {
}
@Override
- public void setSentDate(Date sentDate) throws MessagingException {
+ public void setSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException {
removeHeader("Date");
- addSentDate(sentDate);
+ addSentDate(sentDate, hideTimeZone);
}
public void setInternalSentDate(Date sentDate) {
@@ -333,7 +331,7 @@ public class MimeMessage extends Message {
return "<" + UUID.randomUUID().toString().toUpperCase(Locale.US) + "@" + hostname + ">";
}
- public void setMessageId(String messageId) throws UnavailableStorageException {
+ public void setMessageId(String messageId) throws MessagingException {
setHeader("Message-ID", messageId);
mMessageId = messageId;
}
@@ -419,27 +417,27 @@ public class MimeMessage extends Message {
}
@Override
- public void addHeader(String name, String value) throws UnavailableStorageException {
+ public void addHeader(String name, String value) throws MessagingException {
mHeader.addHeader(name, value);
}
@Override
- public void setHeader(String name, String value) throws UnavailableStorageException {
+ public void setHeader(String name, String value) throws MessagingException {
mHeader.setHeader(name, value);
}
@Override
- public String[] getHeader(String name) throws UnavailableStorageException {
+ public String[] getHeader(String name) throws MessagingException {
return mHeader.getHeader(name);
}
@Override
- public void removeHeader(String name) throws UnavailableStorageException {
+ public void removeHeader(String name) throws MessagingException {
mHeader.removeHeader(name);
}
@Override
- public Set getHeaderNames() throws UnavailableStorageException {
+ public Set getHeaderNames() throws MessagingException {
return mHeader.getHeaderNames();
}
@@ -474,7 +472,7 @@ public class MimeMessage extends Message {
if (mBody instanceof Multipart) {
((Multipart)mBody).setCharset(charset);
} else if (mBody instanceof TextBody) {
- MimeUtility.setCharset(charset, this);
+ CharsetSupport.setCharset(charset, this);
((TextBody)mBody).setCharset(charset);
}
}
@@ -608,28 +606,27 @@ public class MimeMessage extends Message {
/**
* Copy the contents of this object into another {@code MimeMessage} object.
*
- * @param message
- * The {@code MimeMessage} object to receive the contents of this instance.
+ * @param destination The {@code MimeMessage} object to receive the contents of this instance.
*/
- protected void copy(MimeMessage message) {
- super.copy(message);
+ protected void copy(MimeMessage destination) {
+ super.copy(destination);
- message.mHeader = mHeader.clone();
+ destination.mHeader = mHeader.clone();
- message.mBody = mBody;
- message.mMessageId = mMessageId;
- message.mSentDate = mSentDate;
- message.mDateFormat = mDateFormat;
- message.mSize = mSize;
+ destination.mBody = mBody;
+ destination.mMessageId = mMessageId;
+ destination.mSentDate = mSentDate;
+ destination.mDateFormat = mDateFormat;
+ destination.mSize = mSize;
// These arrays are not supposed to be modified, so it's okay to reuse the references
- message.mFrom = mFrom;
- message.mTo = mTo;
- message.mCc = mCc;
- message.mBcc = mBcc;
- message.mReplyTo = mReplyTo;
- message.mReferences = mReferences;
- message.mInReplyTo = mInReplyTo;
+ destination.mFrom = mFrom;
+ destination.mTo = mTo;
+ destination.mCc = mCc;
+ destination.mBcc = mBcc;
+ destination.mReplyTo = mReplyTo;
+ destination.mReferences = mReferences;
+ destination.mInReplyTo = mInReplyTo;
}
@Override
diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java
index c6308a313..a5ac8efa7 100644
--- a/src/com/fsck/k9/mail/internet/MimeUtility.java
+++ b/src/com/fsck/k9/mail/internet/MimeUtility.java
@@ -1,14 +1,12 @@
package com.fsck.k9.mail.internet;
-import android.content.Context;
-import android.util.Log;
-import com.fsck.k9.K9;
-import com.fsck.k9.R;
-import com.fsck.k9.helper.HtmlConverter;
-import com.fsck.k9.mail.*;
-import com.fsck.k9.mail.Message.RecipientType;
-import com.fsck.k9.mail.internet.BinaryTempFileBody.BinaryTempFileBodyInputStream;
+import com.fsck.k9.mail.Body;
+import com.fsck.k9.mail.BodyPart;
+import com.fsck.k9.mail.Message;
+import com.fsck.k9.mail.MessagingException;
+import com.fsck.k9.mail.Multipart;
+import com.fsck.k9.mail.Part;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.codec.Base64InputStream;
@@ -19,31 +17,22 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.nio.charset.Charset;
-import java.nio.charset.IllegalCharsetNameException;
public class MimeUtility {
public static final String DEFAULT_ATTACHMENT_MIME_TYPE = "application/octet-stream";
-
public static final String K9_SETTINGS_MIME_TYPE = "application/x-k9settings";
- private static final String TEXT_DIVIDER =
- "------------------------------------------------------------------------";
-
/*
* http://www.w3schools.com/media/media_mimeref.asp
* +
* http://www.stdicon.com/mimetypes
*/
- public static final String[][] MIME_TYPE_BY_EXTENSION_MAP = new String[][] {
+ private static final String[][] MIME_TYPE_BY_EXTENSION_MAP = new String[][] {
//* Do not delete the next two lines
{ "", DEFAULT_ATTACHMENT_MIME_TYPE },
{ "k9s", K9_SETTINGS_MIME_TYPE},
@@ -896,29 +885,6 @@ public class MimeUtility {
{ "zmm", "application/vnd.handheld-entertainment+xml"}
};
- /**
- * Table for MIME type replacements.
- *
- * Table format: wrong type, correct type
- */
- private static final String[][] MIME_TYPE_REPLACEMENT_MAP = new String[][] {
- {"image/jpg", "image/jpeg"},
- {"image/pjpeg", "image/jpeg"}, // see issue 1712
- {"application/x-zip-compressed", "application/zip"} // see issue 3791
- };
-
- /**
- * Table for character set fall-back.
- *
- * Table format: unsupported charset (regular expression), fall-back charset
- */
- private static final String[][] CHARSET_FALLBACK_MAP = new String[][] {
- // Some Android versions don't support KOI8-U
- {"koi8-u", "koi8-r"},
- {"iso-2022-jp-[\\d]+", "iso-2022-jp"},
- // Default fall-back is US-ASCII
- {".*", "US-ASCII"}
- };
public static String unfold(String s) {
if (s == null) {
@@ -930,9 +896,9 @@ public class MimeUtility {
private static String decode(String s, Message message) {
if (s == null) {
return null;
+ } else {
+ return DecoderUtil.decodeEncodedWords(s, message);
}
-
- return DecoderUtil.decodeEncodedWords(s, message);
}
public static String unfoldAndDecode(String s) {
@@ -952,24 +918,24 @@ public class MimeUtility {
* Returns the named parameter of a header field. If name is null the first
* parameter is returned, or if there are no additional parameters in the
* field the entire field is returned. Otherwise the named parameter is
- * searched for in a case insensitive fashion and returned. If the parameter
- * cannot be found the method returns null.
+ * searched for in a case insensitive fashion and returned.
*
- * @param header
- * @param name
- * @return
+ * @param headerValue the header value
+ * @param parameterName the parameter name
+ * @return the value. if the parameter cannot be found the method returns null.
*/
- public static String getHeaderParameter(String header, String name) {
- if (header == null) {
+ public static String getHeaderParameter(String headerValue, String parameterName) {
+ if (headerValue == null) {
return null;
}
- header = header.replaceAll("\r|\n", "");
- String[] parts = header.split(";");
- if (name == null && parts.length > 0) {
+ headerValue = headerValue.replaceAll("\r|\n", "");
+ String[] parts = headerValue.split(";");
+ if (parameterName == null && parts.length > 0) {
return parts[0].trim();
}
for (String part : parts) {
- if (part.trim().toLowerCase(Locale.US).startsWith(name.toLowerCase(Locale.US))) {
+ if (parameterName != null &&
+ part.trim().toLowerCase(Locale.US).startsWith(parameterName.toLowerCase(Locale.US))) {
String[] partParts = part.split("=", 2);
if (partParts.length == 2) {
String parameter = partParts[1].trim();
@@ -985,12 +951,11 @@ public class MimeUtility {
return null;
}
- public static Part findFirstPartByMimeType(Part part, String mimeType)
- throws MessagingException {
+ public static Part findFirstPartByMimeType(Part part, String mimeType) throws MessagingException {
if (part.getBody() instanceof Multipart) {
Multipart multipart = (Multipart)part.getBody();
for (BodyPart bodyPart : multipart.getBodyParts()) {
- Part ret = findFirstPartByMimeType(bodyPart, mimeType);
+ Part ret = MimeUtility.findFirstPartByMimeType(bodyPart, mimeType);
if (ret != null) {
return ret;
}
@@ -1001,104 +966,6 @@ public class MimeUtility {
return null;
}
- /**
- * Reads the Part's body and returns a String based on any charset conversion that needed
- * to be done. Note, this does not return a text representation of HTML.
- * @param part The part containing a body
- * @return a String containing the converted text in the body, or null if there was no text
- * or an error during conversion.
- */
- public static String getTextFromPart(Part part) {
- try {
- if ((part != null) && (part.getBody() != null)) {
- final Body body = part.getBody();
- if (body instanceof TextBody) {
- return ((TextBody)body).getText();
- }
-
- final String mimeType = part.getMimeType();
- if ((mimeType != null) && MimeUtility.mimeTypeMatches(mimeType, "text/*")) {
- /*
- * We've got a text part, so let's see if it needs to be processed further.
- */
- String charset = getHeaderParameter(part.getContentType(), "charset");
- /*
- * determine the charset from HTML message.
- */
- if (mimeType.equalsIgnoreCase("text/html") && charset == null) {
- InputStream in = part.getBody().getInputStream();
- try {
- byte[] buf = new byte[256];
- in.read(buf, 0, buf.length);
- String str = new String(buf, "US-ASCII");
-
- if (str.isEmpty()) {
- return "";
- }
- Pattern p = Pattern.compile("", Pattern.CASE_INSENSITIVE);
- Matcher m = p.matcher(str);
- if (m.find()) {
- charset = m.group(1);
- }
- } finally {
- try {
- if (in instanceof BinaryTempFileBodyInputStream) {
- /*
- * If this is a BinaryTempFileBodyInputStream, calling close()
- * will delete the file. But we can't let that happen because
- * the file needs to be opened again by the code a few lines
- * down.
- */
- ((BinaryTempFileBodyInputStream) in).closeWithoutDeleting();
- } else {
- in.close();
- }
- } catch (Exception e) { /* ignore */ }
- }
- }
- charset = fixupCharset(charset, getMessageFromPart(part));
-
- /*
- * Now we read the part into a buffer for further processing. Because
- * the stream is now wrapped we'll remove any transfer encoding at this point.
- */
- InputStream in = part.getBody().getInputStream();
- try {
- String text = readToString(in, charset);
-
- // Replace the body with a TextBody that already contains the decoded text
- part.setBody(new TextBody(text));
-
- return text;
- } finally {
- try {
- /*
- * This time we don't care if it's a BinaryTempFileBodyInputStream. We
- * replaced the body with a TextBody instance and hence don't need the
- * file anymore.
- */
- in.close();
- } catch (IOException e) { /* Ignore */ }
- }
- }
- }
-
- } catch (OutOfMemoryError oom) {
- /*
- * If we are not able to process the body there's nothing we can do about it. Return
- * null and let the upper layers handle the missing content.
- */
- Log.e(K9.LOG_TAG, "Unable to getTextFromPart " + oom.toString());
- } catch (Exception e) {
- /*
- * If we are not able to process the body there's nothing we can do about it. Return
- * null and let the upper layers handle the missing content.
- */
- Log.e(K9.LOG_TAG, "Unable to getTextFromPart", e);
- }
- return null;
- }
-
/**
* Returns true if the given mimeType matches the matchAgainst specification.
* @param mimeType A MIME type to check.
@@ -1127,7 +994,7 @@ public class MimeUtility {
*/
if (contentTransferEncoding != null) {
contentTransferEncoding =
- MimeUtility.getHeaderParameter(contentTransferEncoding, null);
+ getHeaderParameter(contentTransferEncoding, null);
if (MimeUtil.ENC_QUOTED_PRINTABLE.equalsIgnoreCase(contentTransferEncoding)) {
in = new QuotedPrintableInputStream(in);
} else if (MimeUtil.ENC_BASE64.equalsIgnoreCase(contentTransferEncoding)) {
@@ -1151,900 +1018,6 @@ public class MimeUtility {
return tempBody;
}
-
- /**
- * Empty base class for the class hierarchy used by
- * {@link MimeUtility#extractTextAndAttachments(Context, Message)}.
- *
- * @see Text
- * @see Html
- * @see MessageHeader
- * @see Alternative
- */
- static abstract class Viewable { /* empty */ }
-
- /**
- * Class representing textual parts of a message that aren't marked as attachments.
- *
- * @see MimeUtility#isPartTextualBody(Part)
- */
- static abstract class Textual extends Viewable {
- private Part mPart;
-
- public Textual(Part part) {
- mPart = part;
- }
-
- public Part getPart() {
- return mPart;
- }
- }
-
- /**
- * Class representing a {@code text/plain} part of a message.
- */
- static class Text extends Textual {
- public Text(Part part) {
- super(part);
- }
- }
-
- /**
- * Class representing a {@code text/html} part of a message.
- */
- static class Html extends Textual {
- public Html(Part part) {
- super(part);
- }
- }
-
- /**
- * Class representing a {@code message/rfc822} part of a message.
- *
- *
- * This is used to extract basic header information when the message contents are displayed
- * inline.
- *
- */
- static class MessageHeader extends Viewable {
- private Part mContainerPart;
- private Message mMessage;
-
- public MessageHeader(Part containerPart, Message message) {
- mContainerPart = containerPart;
- mMessage = message;
- }
-
- public Part getContainerPart() {
- return mContainerPart;
- }
-
- public Message getMessage() {
- return mMessage;
- }
- }
-
- /**
- * Class representing a {@code multipart/alternative} part of a message.
- *
- *
- * Only relevant {@code text/plain} and {@code text/html} children are stored in this container
- * class.
- *
- */
- static class Alternative extends Viewable {
- private List mText;
- private List mHtml;
-
- public Alternative(List text, List html) {
- mText = text;
- mHtml = html;
- }
-
- public List getText() {
- return mText;
- }
-
- public List getHtml() {
- return mHtml;
- }
- }
-
- /**
- * Store viewable text of a message as plain text and HTML, and the parts considered
- * attachments.
- *
- * @see MimeUtility#extractTextAndAttachments(Context, Message)
- */
- public static class ViewableContainer {
- /**
- * The viewable text of the message in plain text.
- */
- public final String text;
-
- /**
- * The viewable text of the message in HTML.
- */
- public final String html;
-
- /**
- * The parts of the message considered attachments (everything not viewable).
- */
- public final List attachments;
-
- ViewableContainer(String text, String html, List attachments) {
- this.text = text;
- this.html = html;
- this.attachments = attachments;
- }
- }
-
- /**
- * Collect attachment parts of a message.
- *
- * @param message
- * The message to collect the attachment parts from.
- *
- * @return A list of parts regarded as attachments.
- *
- * @throws MessagingException
- * In case of an error.
- */
- public static List collectAttachments(Message message)
- throws MessagingException {
- try {
- List attachments = new ArrayList();
- getViewables(message, attachments);
-
- return attachments;
- } catch (Exception e) {
- throw new MessagingException("Couldn't collect attachment parts", e);
- }
- }
-
- /**
- * Collect the viewable textual parts of a message.
- *
- * @param message
- * The message to extract the viewable parts from.
- *
- * @return A set of viewable parts of the message.
- *
- * @throws MessagingException
- * In case of an error.
- */
- public static Set collectTextParts(Message message)
- throws MessagingException {
- try {
- List attachments = new ArrayList();
-
- // Collect all viewable parts
- List viewables = getViewables(message, attachments);
-
- // Extract the Part references
- return getParts(viewables);
- } catch (Exception e) {
- throw new MessagingException("Couldn't extract viewable parts", e);
- }
- }
-
- /**
- * Extract the viewable textual parts of a message and return the rest as attachments.
- *
- * @param context
- * A {@link Context} instance that will be used to get localized strings.
- * @param message
- * The message to extract the text and attachments from.
- *
- * @return A {@link ViewableContainer} instance containing the textual parts of the message as
- * plain text and HTML, and a list of message parts considered attachments.
- *
- * @throws MessagingException
- * In case of an error.
- */
- public static ViewableContainer extractTextAndAttachments(Context context, Message message)
- throws MessagingException {
- try {
- List attachments = new ArrayList();
-
- // Collect all viewable parts
- List viewables = getViewables(message, attachments);
-
- /*
- * Convert the tree of viewable parts into text and HTML
- */
-
- // Used to suppress the divider for the first viewable part
- boolean hideDivider = true;
-
- StringBuilder text = new StringBuilder();
- StringBuilder html = new StringBuilder();
-
- for (Viewable viewable : viewables) {
- if (viewable instanceof Textual) {
- // This is either a text/plain or text/html part. Fill the variables 'text' and
- // 'html', converting between plain text and HTML as necessary.
- text.append(buildText(viewable, !hideDivider));
- html.append(buildHtml(viewable, !hideDivider));
- hideDivider = false;
- } else if (viewable instanceof MessageHeader) {
- MessageHeader header = (MessageHeader) viewable;
- Part containerPart = header.getContainerPart();
- Message innerMessage = header.getMessage();
-
- addTextDivider(text, containerPart, !hideDivider);
- addMessageHeaderText(context, text, innerMessage);
-
- addHtmlDivider(html, containerPart, !hideDivider);
- addMessageHeaderHtml(context, html, innerMessage);
-
- hideDivider = true;
- } else if (viewable instanceof Alternative) {
- // Handle multipart/alternative contents
- Alternative alternative = (Alternative) viewable;
-
- /*
- * We made sure at least one of text/plain or text/html is present when
- * creating the Alternative object. If one part is not present we convert the
- * other one to make sure 'text' and 'html' always contain the same text.
- */
- List textAlternative = alternative.getText().isEmpty() ?
- alternative.getHtml() : alternative.getText();
- List htmlAlternative = alternative.getHtml().isEmpty() ?
- alternative.getText() : alternative.getHtml();
-
- // Fill the 'text' variable
- boolean divider = !hideDivider;
- for (Viewable textViewable : textAlternative) {
- text.append(buildText(textViewable, divider));
- divider = true;
- }
-
- // Fill the 'html' variable
- divider = !hideDivider;
- for (Viewable htmlViewable : htmlAlternative) {
- html.append(buildHtml(htmlViewable, divider));
- divider = true;
- }
- hideDivider = false;
- }
- }
-
- return new ViewableContainer(text.toString(), html.toString(), attachments);
- } catch (Exception e) {
- throw new MessagingException("Couldn't extract viewable parts", e);
- }
- }
-
- /**
- * Traverse the MIME tree of a message an extract viewable parts.
- *
- * @param part
- * The message part to start from.
- * @param attachments
- * A list that will receive the parts that are considered attachments.
- *
- * @return A list of {@link Viewable}s.
- *
- * @throws MessagingException
- * In case of an error.
- */
- private static List getViewables(Part part, List attachments) throws MessagingException {
- List viewables = new ArrayList();
-
- Body body = part.getBody();
- if (body instanceof Multipart) {
- Multipart multipart = (Multipart) body;
- if (part.getMimeType().equalsIgnoreCase("multipart/alternative")) {
- /*
- * For multipart/alternative parts we try to find a text/plain and a text/html
- * child. Everything else we find is put into 'attachments'.
- */
- List text = findTextPart(multipart, true);
-
- Set knownTextParts = getParts(text);
- List html = findHtmlPart(multipart, knownTextParts, attachments, true);
-
- if (!text.isEmpty() || !html.isEmpty()) {
- Alternative alternative = new Alternative(text, html);
- viewables.add(alternative);
- }
- } else {
- // For all other multipart parts we recurse to grab all viewable children.
- for (Part bodyPart : multipart.getBodyParts()) {
- viewables.addAll(getViewables(bodyPart, attachments));
- }
- }
- } else if (body instanceof Message &&
- !("attachment".equalsIgnoreCase(getContentDisposition(part)))) {
- /*
- * We only care about message/rfc822 parts whose Content-Disposition header has a value
- * other than "attachment".
- */
- Message message = (Message) body;
-
- // We add the Message object so we can extract the filename later.
- viewables.add(new MessageHeader(part, message));
-
- // Recurse to grab all viewable parts and attachments from that message.
- viewables.addAll(getViewables(message, attachments));
- } else if (isPartTextualBody(part)) {
- /*
- * Save text/plain and text/html
- */
- String mimeType = part.getMimeType();
- if (mimeType.equalsIgnoreCase("text/plain")) {
- Text text = new Text(part);
- viewables.add(text);
- } else {
- Html html = new Html(part);
- viewables.add(html);
- }
- } else {
- // Everything else is treated as attachment.
- attachments.add(part);
- }
-
- return viewables;
- }
-
- /**
- * Search the children of a {@link Multipart} for {@code text/plain} parts.
- *
- * @param multipart
- * The {@code Multipart} to search through.
- * @param directChild
- * If {@code true}, this method will return after the first {@code text/plain} was
- * found.
- *
- * @return A list of {@link Text} viewables.
- *
- * @throws MessagingException
- * In case of an error.
- */
- private static List findTextPart(Multipart multipart, boolean directChild)
- throws MessagingException {
- List viewables = new ArrayList();
-
- for (Part part : multipart.getBodyParts()) {
- Body body = part.getBody();
- if (body instanceof Multipart) {
- Multipart innerMultipart = (Multipart) body;
-
- /*
- * Recurse to find text parts. Since this is a multipart that is a child of a
- * multipart/alternative we don't want to stop after the first text/plain part
- * we find. This will allow to get all text parts for constructions like this:
- *
- * 1. multipart/alternative
- * 1.1. multipart/mixed
- * 1.1.1. text/plain
- * 1.1.2. text/plain
- * 1.2. text/html
- */
- List textViewables = findTextPart(innerMultipart, false);
-
- if (!textViewables.isEmpty()) {
- viewables.addAll(textViewables);
- if (directChild) {
- break;
- }
- }
- } else if (isPartTextualBody(part) && part.getMimeType().equalsIgnoreCase("text/plain")) {
- Text text = new Text(part);
- viewables.add(text);
- if (directChild) {
- break;
- }
- }
- }
-
- return viewables;
- }
-
- /**
- * Search the children of a {@link Multipart} for {@code text/html} parts.
- *
- *
- * Every part that is not a {@code text/html} we want to display, we add to 'attachments'.
- *
- *
- * @param multipart
- * The {@code Multipart} to search through.
- * @param knownTextParts
- * A set of {@code text/plain} parts that shouldn't be added to 'attachments'.
- * @param attachments
- * A list that will receive the parts that are considered attachments.
- * @param directChild
- * If {@code true}, this method will add all {@code text/html} parts except the first
- * found to 'attachments'.
- *
- * @return A list of {@link Text} viewables.
- *
- * @throws MessagingException
- * In case of an error.
- */
- private static List findHtmlPart(Multipart multipart, Set knownTextParts,
- List attachments, boolean directChild) throws MessagingException {
- List viewables = new ArrayList();
-
- boolean partFound = false;
- for (Part part : multipart.getBodyParts()) {
- Body body = part.getBody();
- if (body instanceof Multipart) {
- Multipart innerMultipart = (Multipart) body;
-
- if (directChild && partFound) {
- // We already found our text/html part. Now we're only looking for attachments.
- findAttachments(innerMultipart, knownTextParts, attachments);
- } else {
- /*
- * Recurse to find HTML parts. Since this is a multipart that is a child of a
- * multipart/alternative we don't want to stop after the first text/html part
- * we find. This will allow to get all text parts for constructions like this:
- *
- * 1. multipart/alternative
- * 1.1. text/plain
- * 1.2. multipart/mixed
- * 1.2.1. text/html
- * 1.2.2. text/html
- * 1.3. image/jpeg
- */
- List htmlViewables = findHtmlPart(innerMultipart, knownTextParts,
- attachments, false);
-
- if (!htmlViewables.isEmpty()) {
- partFound = true;
- viewables.addAll(htmlViewables);
- }
- }
- } else if (!(directChild && partFound) && isPartTextualBody(part) &&
- part.getMimeType().equalsIgnoreCase("text/html")) {
- Html html = new Html(part);
- viewables.add(html);
- partFound = true;
- } else if (!knownTextParts.contains(part)) {
- // Only add this part as attachment if it's not a viewable text/plain part found
- // earlier.
- attachments.add(part);
- }
- }
-
- return viewables;
- }
-
- /**
- * Build a set of message parts for fast lookups.
- *
- * @param viewables
- * A list of {@link Viewable}s containing references to the message parts to include in
- * the set.
- *
- * @return The set of viewable {@code Part}s.
- *
- * @see MimeUtility#findHtmlPart(Multipart, Set, List, boolean)
- * @see MimeUtility#findAttachments(Multipart, Set, List)
- */
- private static Set getParts(List viewables) {
- Set parts = new HashSet();
-
- for (Viewable viewable : viewables) {
- if (viewable instanceof Textual) {
- parts.add(((Textual) viewable).getPart());
- } else if (viewable instanceof Alternative) {
- Alternative alternative = (Alternative) viewable;
- parts.addAll(getParts(alternative.getText()));
- parts.addAll(getParts(alternative.getHtml()));
- }
- }
-
- return parts;
- }
-
- /**
- * Traverse the MIME tree and add everything that's not a known text part to 'attachments'.
- *
- * @param multipart
- * The {@link Multipart} to start from.
- * @param knownTextParts
- * A set of known text parts we don't want to end up in 'attachments'.
- * @param attachments
- * A list that will receive the parts that are considered attachments.
- */
- private static void findAttachments(Multipart multipart, Set knownTextParts,
- List attachments) {
- for (Part part : multipart.getBodyParts()) {
- Body body = part.getBody();
- if (body instanceof Multipart) {
- Multipart innerMultipart = (Multipart) body;
- findAttachments(innerMultipart, knownTextParts, attachments);
- } else if (!knownTextParts.contains(part)) {
- attachments.add(part);
- }
- }
- }
-
- /**
- * Extract important header values from a message to display inline (plain text version).
- *
- * @param context
- * A {@link Context} instance that will be used to get localized strings.
- * @param text
- * The {@link StringBuilder} that will receive the (plain text) output.
- * @param message
- * The message to extract the header values from.
- *
- * @throws MessagingException
- * In case of an error.
- */
- private static void addMessageHeaderText(Context context, StringBuilder text, Message message)
- throws MessagingException {
- // From:
- Address[] from = message.getFrom();
- if (from != null && from.length > 0) {
- text.append(context.getString(R.string.message_compose_quote_header_from));
- text.append(' ');
- text.append(Address.toString(from));
- text.append("\r\n");
- }
-
- // To:
- Address[] to = message.getRecipients(RecipientType.TO);
- if (to != null && to.length > 0) {
- text.append(context.getString(R.string.message_compose_quote_header_to));
- text.append(' ');
- text.append(Address.toString(to));
- text.append("\r\n");
- }
-
- // Cc:
- Address[] cc = message.getRecipients(RecipientType.CC);
- if (cc != null && cc.length > 0) {
- text.append(context.getString(R.string.message_compose_quote_header_cc));
- text.append(' ');
- text.append(Address.toString(cc));
- text.append("\r\n");
- }
-
- // Date:
- Date date = message.getSentDate();
- if (date != null) {
- text.append(context.getString(R.string.message_compose_quote_header_send_date));
- text.append(' ');
- text.append(date.toString());
- text.append("\r\n");
- }
-
- // Subject:
- String subject = message.getSubject();
- text.append(context.getString(R.string.message_compose_quote_header_subject));
- text.append(' ');
- if (subject == null) {
- text.append(context.getString(R.string.general_no_subject));
- } else {
- text.append(subject);
- }
- text.append("\r\n\r\n");
- }
-
- /**
- * Extract important header values from a message to display inline (HTML version).
- *
- * @param context
- * A {@link Context} instance that will be used to get localized strings.
- * @param html
- * The {@link StringBuilder} that will receive the (HTML) output.
- * @param message
- * The message to extract the header values from.
- *
- * @throws MessagingException
- * In case of an error.
- */
- private static void addMessageHeaderHtml(Context context, StringBuilder html, Message message)
- throws MessagingException {
-
- html.append("");
-
- // From:
- Address[] from = message.getFrom();
- if (from != null && from.length > 0) {
- addTableRow(html, context.getString(R.string.message_compose_quote_header_from),
- Address.toString(from));
- }
-
- // To:
- Address[] to = message.getRecipients(RecipientType.TO);
- if (to != null && to.length > 0) {
- addTableRow(html, context.getString(R.string.message_compose_quote_header_to),
- Address.toString(to));
- }
-
- // Cc:
- Address[] cc = message.getRecipients(RecipientType.CC);
- if (cc != null && cc.length > 0) {
- addTableRow(html, context.getString(R.string.message_compose_quote_header_cc),
- Address.toString(cc));
- }
-
- // Date:
- Date date = message.getSentDate();
- if (date != null) {
- addTableRow(html, context.getString(R.string.message_compose_quote_header_send_date),
- date.toString());
- }
-
- // Subject:
- String subject = message.getSubject();
- addTableRow(html, context.getString(R.string.message_compose_quote_header_subject),
- (subject == null) ? context.getString(R.string.general_no_subject) : subject);
-
- html.append("
");
- }
-
- /**
- * Output an HTML table two column row with some hardcoded style.
- *
- * @param html
- * The {@link StringBuilder} that will receive the output.
- * @param header
- * The string to be put in the {@code TH} element.
- * @param value
- * The string to be put in the {@code TD} element.
- */
- private static void addTableRow(StringBuilder html, String header, String value) {
- html.append("");
- html.append(header);
- html.append(" | ");
- html.append("");
- html.append(value);
- html.append(" |
");
- }
-
- /**
- * Use the contents of a {@link Viewable} to create the plain text to be displayed.
- *
- *
- * This will use {@link HtmlConverter#htmlToText(String)} to convert HTML parts to plain text
- * if necessary.
- *
- *
- * @param viewable
- * The viewable part to build the text from.
- * @param prependDivider
- * {@code true}, if the text divider should be inserted as first element.
- * {@code false}, otherwise.
- *
- * @return The contents of the supplied viewable instance as plain text.
- */
- private static StringBuilder buildText(Viewable viewable, boolean prependDivider)
- {
- StringBuilder text = new StringBuilder();
- if (viewable instanceof Textual) {
- Part part = ((Textual)viewable).getPart();
- addTextDivider(text, part, prependDivider);
-
- String t = getTextFromPart(part);
- if (t == null) {
- t = "";
- } else if (viewable instanceof Html) {
- t = HtmlConverter.htmlToText(t);
- }
- text.append(t);
- } else if (viewable instanceof Alternative) {
- // That's odd - an Alternative as child of an Alternative; go ahead and try to use the
- // text/plain child; fall-back to the text/html part.
- Alternative alternative = (Alternative) viewable;
-
- List textAlternative = alternative.getText().isEmpty() ?
- alternative.getHtml() : alternative.getText();
-
- boolean divider = prependDivider;
- for (Viewable textViewable : textAlternative) {
- text.append(buildText(textViewable, divider));
- divider = true;
- }
- }
-
- return text;
- }
-
- /*
- * Some constants that are used by addTextDivider() below.
- */
- private static final int TEXT_DIVIDER_LENGTH = TEXT_DIVIDER.length();
- private static final String FILENAME_PREFIX = "----- ";
- private static final int FILENAME_PREFIX_LENGTH = FILENAME_PREFIX.length();
- private static final String FILENAME_SUFFIX = " ";
- private static final int FILENAME_SUFFIX_LENGTH = FILENAME_SUFFIX.length();
-
- /**
- * Add a plain text divider between two plain text message parts.
- *
- * @param text
- * The {@link StringBuilder} to append the divider to.
- * @param part
- * The message part that will follow after the divider. This is used to extract the
- * part's name.
- * @param prependDivider
- * {@code true}, if the divider should be appended. {@code false}, otherwise.
- */
- private static void addTextDivider(StringBuilder text, Part part, boolean prependDivider) {
- if (prependDivider) {
- String filename = getPartName(part);
-
- text.append("\r\n\r\n");
- int len = filename.length();
- if (len > 0) {
- if (len > TEXT_DIVIDER_LENGTH - FILENAME_PREFIX_LENGTH - FILENAME_SUFFIX_LENGTH) {
- filename = filename.substring(0, TEXT_DIVIDER_LENGTH - FILENAME_PREFIX_LENGTH -
- FILENAME_SUFFIX_LENGTH - 3) + "...";
- }
- text.append(FILENAME_PREFIX);
- text.append(filename);
- text.append(FILENAME_SUFFIX);
- text.append(TEXT_DIVIDER.substring(0, TEXT_DIVIDER_LENGTH -
- FILENAME_PREFIX_LENGTH - filename.length() - FILENAME_SUFFIX_LENGTH));
- } else {
- text.append(TEXT_DIVIDER);
- }
- text.append("\r\n\r\n");
- }
- }
-
- /**
- * Use the contents of a {@link Viewable} to create the HTML to be displayed.
- *
- *
- * This will use {@link HtmlConverter#textToHtml(String)} to convert plain text parts
- * to HTML if necessary.
- *
- *
- * @param viewable
- * The viewable part to build the HTML from.
- * @param prependDivider
- * {@code true}, if the HTML divider should be inserted as first element.
- * {@code false}, otherwise.
- *
- * @return The contents of the supplied viewable instance as HTML.
- */
- private static StringBuilder buildHtml(Viewable viewable, boolean prependDivider)
- {
- StringBuilder html = new StringBuilder();
- if (viewable instanceof Textual) {
- Part part = ((Textual)viewable).getPart();
- addHtmlDivider(html, part, prependDivider);
-
- String t = getTextFromPart(part);
- if (t == null) {
- t = "";
- } else if (viewable instanceof Text) {
- t = HtmlConverter.textToHtml(t);
- }
- html.append(t);
- } else if (viewable instanceof Alternative) {
- // That's odd - an Alternative as child of an Alternative; go ahead and try to use the
- // text/html child; fall-back to the text/plain part.
- Alternative alternative = (Alternative) viewable;
-
- List htmlAlternative = alternative.getHtml().isEmpty() ?
- alternative.getText() : alternative.getHtml();
-
- boolean divider = prependDivider;
- for (Viewable htmlViewable : htmlAlternative) {
- html.append(buildHtml(htmlViewable, divider));
- divider = true;
- }
- }
-
- return html;
- }
-
- /**
- * Add an HTML divider between two HTML message parts.
- *
- * @param html
- * The {@link StringBuilder} to append the divider to.
- * @param part
- * The message part that will follow after the divider. This is used to extract the
- * part's name.
- * @param prependDivider
- * {@code true}, if the divider should be appended. {@code false}, otherwise.
- */
- private static void addHtmlDivider(StringBuilder html, Part part, boolean prependDivider) {
- if (prependDivider) {
- String filename = getPartName(part);
-
- html.append("");
- html.append(filename);
- html.append("
");
- }
- }
-
- /**
- * Get the name of the message part.
- *
- * @param part
- * The part to get the name for.
- *
- * @return The (file)name of the part if available. An empty string, otherwise.
- */
- private static String getPartName(Part part) {
- try {
- String disposition = part.getDisposition();
- if (disposition != null) {
- String name = MimeUtility.getHeaderParameter(disposition, "filename");
- return (name == null) ? "" : name;
- }
- }
- catch (MessagingException e) { /* ignore */ }
-
- return "";
- }
-
- /**
- * Get the value of the {@code Content-Disposition} header.
- *
- * @param part
- * The message part to read the header from.
- *
- * @return The value of the {@code Content-Disposition} header if available. {@code null},
- * otherwise.
- */
- private static String getContentDisposition(Part part) {
- try {
- String disposition = part.getDisposition();
- if (disposition != null) {
- return MimeUtility.getHeaderParameter(disposition, null);
- }
- }
- catch (MessagingException e) { /* ignore */ }
-
- return null;
- }
-
- private static Boolean isPartTextualBody(Part part) throws MessagingException {
- String disposition = part.getDisposition();
- String dispositionType = null;
- String dispositionFilename = null;
- if (disposition != null) {
- dispositionType = MimeUtility.getHeaderParameter(disposition, null);
- dispositionFilename = MimeUtility.getHeaderParameter(disposition, "filename");
- }
-
- /*
- * A best guess that this part is intended to be an attachment and not inline.
- */
- boolean attachment = ("attachment".equalsIgnoreCase(dispositionType) || (dispositionFilename != null));
-
- if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/html"))) {
- return true;
- }
- /*
- * If the part is plain text and it got this far it's part of a
- * mixed (et al) and should be rendered inline.
- */
- else if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/plain"))) {
- return true;
- }
- /*
- * Finally, if it's nothing else we will include it as an attachment.
- */
- else {
- return false;
- }
- }
-
- public static String getCharsetFromAddress(String address) {
- String variant = getJisVariantFromAddress(address);
- if (variant != null) {
- String charset = "x-" + variant + "-shift_jis-2007";
- if (Charset.isSupported(charset))
- return charset;
- }
-
- return "UTF-8";
- }
-
public static String getMimeTypeByExtension(String filename) {
String returnedType = null;
String extension = null;
@@ -2079,50 +1052,6 @@ public class MimeUtility {
return null;
}
- /**
- * Convert some wrong MIME types encountered in the wild to canonical MIME types.
- *
- * @param mimeType
- * The original MIME type
- *
- * @return If {@code mimeType} is known to be wrong the correct MIME type is returned.
- * Otherwise the lower case version of {@code mimeType} is returned.
- *
- * @see #MIME_TYPE_REPLACEMENT_MAP
- */
- private static String canonicalizeMimeType(String mimeType) {
- String lowerCaseMimeType = mimeType.toLowerCase(Locale.US);
- for (String[] mimeTypeMapEntry : MIME_TYPE_REPLACEMENT_MAP) {
- if (mimeTypeMapEntry[0].equals(lowerCaseMimeType)) {
- return mimeTypeMapEntry[1];
- }
- }
- return lowerCaseMimeType;
- }
-
- /**
- * When viewing the attachment we want the MIME type to be as sensible as possible. So we fix
- * it up if necessary.
- *
- * @param mimeType
- * The original MIME type of the attachment.
- * @param name
- * The (file)name of the attachment.
- *
- * @return The best MIME type we can come up with.
- */
- public static String getMimeTypeForViewing(String mimeType, String name) {
- if (DEFAULT_ATTACHMENT_MIME_TYPE.equalsIgnoreCase(mimeType)) {
- // If the MIME type is the generic "application/octet-stream"
- // we try to find a better one by looking at the file extension.
- return getMimeTypeByExtension(name);
- }
-
- // Some messages contain wrong MIME types. See if we know better.
- return canonicalizeMimeType(mimeType);
- }
-
-
/**
* Get a default content-transfer-encoding for use with a given content-type
* when adding an unencoded attachment. It's possible that 8bit encodings
@@ -2153,1240 +1082,4 @@ public class MimeUtility {
return (MimeUtil.ENC_BASE64);
}
}
-
- private static Message getMessageFromPart(Part part) {
- while (part != null) {
- if (part instanceof Message)
- return (Message)part;
-
- if (!(part instanceof BodyPart))
- return null;
-
- Multipart multipart = ((BodyPart)part).getParent();
- if (multipart == null)
- return null;
-
- part = multipart.getParent();
- }
- return null;
- }
-
- public static String fixupCharset(String charset, Message message) throws MessagingException {
- if (charset == null || "0".equals(charset))
- charset = "US-ASCII"; // No encoding, so use us-ascii, which is the standard.
-
- charset = charset.toLowerCase(Locale.US);
- if (charset.equals("cp932"))
- charset = "shift_jis";
-
- if (charset.equals("shift_jis") || charset.equals("iso-2022-jp")) {
- String variant = getJisVariantFromMessage(message);
- if (variant != null)
- charset = "x-" + variant + "-" + charset + "-2007";
- }
- return charset;
- }
-
- private static String getJisVariantFromMessage(Message message) throws MessagingException {
- if (message == null)
- return null;
-
- // If a receiver is known to use a JIS variant, the sender transfers the message after converting the
- // charset as a convention.
- String variant = getJisVariantFromReceivedHeaders(message);
- if (variant != null)
- return variant;
-
- // If a receiver is not known to use any JIS variants, the sender transfers the message without converting
- // the charset.
- variant = getJisVariantFromFromHeaders(message);
- if (variant != null)
- return variant;
-
- return getJisVariantFromMailerHeaders(message);
- }
-
- private static String getJisVariantFromReceivedHeaders(Message message) throws MessagingException {
- String receivedHeaders[] = message.getHeader("Received");
- if (receivedHeaders == null)
- return null;
-
- for (String receivedHeader : receivedHeaders) {
- String address = getAddressFromReceivedHeader(receivedHeader);
- if (address == null)
- continue;
- String variant = getJisVariantFromAddress(address);
- if (variant != null)
- return variant;
- }
- return null;
- }
-
- private static String getAddressFromReceivedHeader(String receivedHeader) {
- // Not implemented yet! Extract an address from the FOR clause of the given Received header.
- return null;
- }
-
- private static String getJisVariantFromFromHeaders(Message message) throws MessagingException {
- Address addresses[] = message.getFrom();
- if (addresses == null || addresses.length == 0)
- return null;
-
- return getJisVariantFromAddress(addresses[0].getAddress());
- }
-
- private static String getJisVariantFromAddress(String address) {
- if (address == null)
- return null;
- if (isInDomain(address, "docomo.ne.jp") || isInDomain(address, "dwmail.jp") ||
- isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com") ||
- isInDomain(address, "emnet.ne.jp") || isInDomain(address, "emobile.ne.jp"))
- return "docomo";
- else if (isInDomain(address, "softbank.ne.jp") || isInDomain(address, "vodafone.ne.jp") ||
- isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp"))
- return "softbank";
- else if (isInDomain(address, "ezweb.ne.jp") || isInDomain(address, "ido.ne.jp"))
- return "kddi";
- return null;
- }
-
- private static boolean isInDomain(String address, String domain) {
- int index = address.length() - domain.length() - 1;
- if (index < 0)
- return false;
-
- char c = address.charAt(index);
- if (c != '@' && c != '.')
- return false;
-
- return address.endsWith(domain);
- }
-
- private static String getJisVariantFromMailerHeaders(Message message) throws MessagingException {
- String mailerHeaders[] = message.getHeader("X-Mailer");
- if (mailerHeaders == null || mailerHeaders.length == 0)
- return null;
-
- if (mailerHeaders[0].startsWith("iPhone Mail ") || mailerHeaders[0].startsWith("iPad Mail "))
- return "iphone";
-
- return null;
- }
-
- public static String readToString(InputStream in, String charset) throws IOException {
- boolean isIphoneString = false;
-
- // iso-2022-jp variants are supported by no versions as of Dec 2010.
- if (charset.length() > 19 && charset.startsWith("x-") &&
- charset.endsWith("-iso-2022-jp-2007") && !Charset.isSupported(charset)) {
- in = new Iso2022JpToShiftJisInputStream(in);
- charset = "x-" + charset.substring(2, charset.length() - 17) + "-shift_jis-2007";
- }
-
- // shift_jis variants are supported by Eclair and later.
- if (charset.length() > 17 && charset.startsWith("x-") &&
- charset.endsWith("-shift_jis-2007") && !Charset.isSupported(charset)) {
- // If the JIS variant is iPhone, map the Unicode private use area in iPhone to the one in Android after
- // converting the character set from the standard Shift JIS to Unicode.
- if (charset.substring(2, charset.length() - 15).equals("iphone"))
- isIphoneString = true;
-
- charset = "shift_jis";
- }
-
- /*
- * See if there is conversion from the MIME charset to the Java one.
- * this function may also throw an exception if the charset name is not known
- */
- boolean supported;
- try {
- supported = Charset.isSupported(charset);
- } catch (IllegalCharsetNameException e) {
- supported = false;
- }
-
- for (String[] rule: CHARSET_FALLBACK_MAP) {
- if (supported) {
- break;
- }
-
- if (charset.matches(rule[0])) {
- Log.e(K9.LOG_TAG, "I don't know how to deal with the charset " + charset +
- ". Falling back to " + rule[1]);
- charset = rule[1];
- try {
- supported = Charset.isSupported(charset);
- } catch (IllegalCharsetNameException e) {
- supported = false;
- }
- }
- }
-
- /*
- * Convert and return as new String
- */
- String str = IOUtils.toString(in, charset);
-
- if (isIphoneString)
- str = importStringFromIphone(str);
- return str;
- }
-
- static private String importStringFromIphone(String str) {
- StringBuilder buff = new StringBuilder(str.length());
- for (int i = 0; i < str.length(); i = str.offsetByCodePoints(i, 1)) {
- int codePoint = str.codePointAt(i);
- buff.appendCodePoint(importCodePointFromIphone(codePoint));
- }
- return buff.toString();
- }
-
- static private int importCodePointFromIphone(int codePoint) {
- switch (codePoint) {
- case 0xE001:
- return 0xFE19B;
- case 0xE002:
- return 0xFE19C;
- case 0xE003:
- return 0xFE823;
- case 0xE004:
- return 0xFE19D;
- case 0xE005:
- return 0xFE19E;
- case 0xE006:
- return 0xFE4CF;
- case 0xE007:
- return 0xFE4CD;
- case 0xE008:
- return 0xFE4EF;
- case 0xE009:
- return 0xFE523;
- case 0xE00A:
- return 0xFE525;
- case 0xE00B:
- return 0xFE528;
- case 0xE00C:
- return 0xFE538;
- case 0xE00D:
- return 0xFEB96;
- case 0xE00E:
- return 0xFEB97;
- case 0xE00F:
- return 0xFEB98;
- case 0xE010:
- return 0xFEB93;
- case 0xE011:
- return 0xFEB94;
- case 0xE012:
- return 0xFEB95;
- case 0xE013:
- return 0xFE7D5;
- case 0xE014:
- return 0xFE7D2;
- case 0xE015:
- return 0xFE7D3;
- case 0xE016:
- return 0xFE7D1;
- case 0xE017:
- return 0xFE7DA;
- case 0xE018:
- return 0xFE7D4;
- case 0xE019:
- return 0xFE1BD;
- case 0xE01A:
- return 0xFE1BE;
- case 0xE01B:
- return 0xFE7E4;
- case 0xE01C:
- return 0xFE7EA;
- case 0xE01D:
- return 0xFE7E9;
- case 0xE01E:
- return 0xFE7DF;
- case 0xE01F:
- return 0xFE7E3;
- case 0xE020:
- return 0xFEB09;
- case 0xE021:
- return 0xFEB04;
- case 0xE022:
- return 0xFEB0C;
- case 0xE023:
- return 0xFEB0E;
- case 0xE024:
- return 0xFE01E;
- case 0xE025:
- return 0xFE01F;
- case 0xE026:
- return 0xFE020;
- case 0xE027:
- return 0xFE021;
- case 0xE028:
- return 0xFE022;
- case 0xE029:
- return 0xFE023;
- case 0xE02A:
- return 0xFE024;
- case 0xE02B:
- return 0xFE025;
- case 0xE02C:
- return 0xFE026;
- case 0xE02D:
- return 0xFE027;
- case 0xE02E:
- return 0xFE028;
- case 0xE02F:
- return 0xFE029;
- case 0xE030:
- return 0xFE040;
- case 0xE031:
- return 0xFE4D2;
- case 0xE032:
- return 0xFE041;
- case 0xE033:
- return 0xFE512;
- case 0xE034:
- return 0xFE825;
- case 0xE035:
- return 0xFE826;
- case 0xE036:
- return 0xFE4B0;
- case 0xE037:
- return 0xFE4BB;
- case 0xE038:
- return 0xFE4B2;
- case 0xE039:
- return 0xFE7EC;
- case 0xE03A:
- return 0xFE7F5;
- case 0xE03B:
- return 0xFE4C3;
- case 0xE03C:
- return 0xFE800;
- case 0xE03D:
- return 0xFE801;
- case 0xE03E:
- return 0xFE813;
- case 0xE03F:
- return 0xFEB82;
- case 0xE040:
- return 0xFE815;
- case 0xE041:
- return 0xFE816;
- case 0xE042:
- return 0xFE818;
- case 0xE043:
- return 0xFE980;
- case 0xE044:
- return 0xFE982;
- case 0xE045:
- return 0xFE981;
- case 0xE046:
- return 0xFE962;
- case 0xE047:
- return 0xFE983;
- case 0xE048:
- return 0xFE003;
- case 0xE049:
- return 0xFE001;
- case 0xE04A:
- return 0xFE000;
- case 0xE04B:
- return 0xFE002;
- case 0xE04C:
- return 0xFE014;
- case 0xE04D:
- return 0xFE009;
- case 0xE04E:
- return 0xFE1AF;
- case 0xE04F:
- return 0xFE1B8;
- case 0xE050:
- return 0xFE1C0;
- case 0xE051:
- return 0xFE1C1;
- case 0xE052:
- return 0xFE1B7;
- case 0xE053:
- return 0xFE1C2;
- case 0xE054:
- return 0xFE1C3;
- case 0xE055:
- return 0xFE1BC;
- case 0xE056:
- return 0xFE335;
- case 0xE057:
- return 0xFE330;
- case 0xE058:
- return 0xFE323;
- case 0xE059:
- return 0xFE320;
- case 0xE05A:
- return 0xFE4F4;
- case 0xE101:
- return 0xFE52D;
- case 0xE102:
- return 0xFE52E;
- case 0xE103:
- return 0xFE52B;
- case 0xE104:
- return 0xFE526;
- case 0xE105:
- return 0xFE329;
- case 0xE106:
- return 0xFE327;
- case 0xE107:
- return 0xFE341;
- case 0xE108:
- return 0xFE344;
- case 0xE109:
- return 0xFE1C4;
- case 0xE10A:
- return 0xFE1C5;
- case 0xE10B:
- return 0xFE1BF;
- case 0xE10C:
- return 0xFE1B0;
- case 0xE10D:
- return 0xFE7ED;
- case 0xE10E:
- return 0xFE4D1;
- case 0xE10F:
- return 0xFEB56;
- case 0xE110:
- return 0xFE03C;
- case 0xE111:
- return 0xFE827;
- case 0xE112:
- return 0xFE510;
- case 0xE113:
- return 0xFE4F5;
- case 0xE114:
- return 0xFEB85;
- case 0xE115:
- return 0xFE7D9;
- case 0xE116:
- return 0xFE4CA;
- case 0xE117:
- return 0xFE515;
- case 0xE118:
- return 0xFE03F;
- case 0xE119:
- return 0xFE042;
- case 0xE11A:
- return 0xFE1B2;
- case 0xE11B:
- return 0xFE1AE;
- case 0xE11C:
- return 0xFE1B3;
- case 0xE11D:
- return 0xFE4F6;
- case 0xE11E:
- return 0xFE53B;
- case 0xE11F:
- return 0xFE537;
- case 0xE120:
- return 0xFE960;
- case 0xE121:
- return 0xFE4BC;
- case 0xE122:
- return 0xFE7FB;
- case 0xE123:
- return 0xFE7FA;
- case 0xE124:
- return 0xFE7FD;
- case 0xE125:
- return 0xFE807;
- case 0xE126:
- return 0xFE81D;
- case 0xE127:
- return 0xFE81E;
- case 0xE128:
- return 0xFE81F;
- case 0xE129:
- return 0xFE820;
- case 0xE12A:
- return 0xFE81C;
- case 0xE12B:
- return 0xFE1B1;
- case 0xE12C:
- return 0xFE81B;
- case 0xE12D:
- return 0xFE80B;
- case 0xE12E:
- return 0xFEB32;
- case 0xE12F:
- return 0xFE4DD;
- case 0xE130:
- return 0xFE80C;
- case 0xE131:
- return 0xFE7DB;
- case 0xE132:
- return 0xFE7D7;
- case 0xE133:
- return 0xFE80D;
- case 0xE134:
- return 0xFE7DC;
- case 0xE135:
- return 0xFE7EE;
- case 0xE136:
- return 0xFE7EB;
- case 0xE137:
- return 0xFE7F8;
- case 0xE138:
- return 0xFEB33;
- case 0xE139:
- return 0xFEB34;
- case 0xE13A:
- return 0xFEB35;
- case 0xE13B:
- return 0xFE509;
- case 0xE13C:
- return 0xFEB59;
- case 0xE13D:
- return 0xFE004;
- case 0xE13E:
- return 0xFE4D6;
- case 0xE13F:
- return 0xFE505;
- case 0xE140:
- return 0xFE507;
- case 0xE141:
- return 0xFE821;
- case 0xE142:
- return 0xFE52F;
- case 0xE143:
- return 0xFE514;
- case 0xE144:
- return 0xFEB86;
- case 0xE145:
- return 0xFEB87;
- case 0xE146:
- return 0xFE00B;
- case 0xE147:
- return 0xFE965;
- case 0xE148:
- return 0xFE546;
- case 0xE149:
- return 0xFE4DE;
- case 0xE14A:
- return 0xFE4DF;
- case 0xE14B:
- return 0xFE531;
- case 0xE14C:
- return 0xFEB5E;
- case 0xE14D:
- return 0xFE4B5;
- case 0xE14E:
- return 0xFE7F7;
- case 0xE14F:
- return 0xFE7F6;
- case 0xE150:
- return 0xFE7E7;
- case 0xE151:
- return 0xFE506;
- case 0xE152:
- return 0xFE1A1;
- case 0xE153:
- return 0xFE4B3;
- case 0xE154:
- return 0xFE4B6;
- case 0xE155:
- return 0xFE4B4;
- case 0xE156:
- return 0xFE4B9;
- case 0xE157:
- return 0xFE4BA;
- case 0xE158:
- return 0xFE4B7;
- case 0xE159:
- return 0xFE7E6;
- case 0xE15A:
- return 0xFE7EF;
- case 0xE201:
- return 0xFE7F0;
- case 0xE202:
- return 0xFE7E8;
- case 0xE203:
- return 0xFEB24;
- case 0xE204:
- return 0xFEB19;
- case 0xE205:
- return 0xFEB61;
- case 0xE206:
- return 0xFEB62;
- case 0xE207:
- return 0xFEB25;
- case 0xE208:
- return 0xFEB1F;
- case 0xE209:
- return 0xFE044;
- case 0xE20A:
- return 0xFEB20;
- case 0xE20B:
- return 0xFE838;
- case 0xE20C:
- return 0xFEB1A;
- case 0xE20D:
- return 0xFEB1C;
- case 0xE20E:
- return 0xFEB1B;
- case 0xE20F:
- return 0xFEB1D;
- case 0xE210:
- return 0xFE82C;
- case 0xE211:
- return 0xFE82B;
- case 0xE212:
- return 0xFEB36;
- case 0xE213:
- return 0xFEB37;
- case 0xE214:
- return 0xFEB38;
- case 0xE215:
- return 0xFEB39;
- case 0xE216:
- return 0xFEB3A;
- case 0xE217:
- return 0xFEB3B;
- case 0xE218:
- return 0xFEB3C;
- case 0xE219:
- return 0xFEB63;
- case 0xE21A:
- return 0xFEB64;
- case 0xE21B:
- return 0xFEB67;
- case 0xE21C:
- return 0xFE82E;
- case 0xE21D:
- return 0xFE82F;
- case 0xE21E:
- return 0xFE830;
- case 0xE21F:
- return 0xFE831;
- case 0xE220:
- return 0xFE832;
- case 0xE221:
- return 0xFE833;
- case 0xE222:
- return 0xFE834;
- case 0xE223:
- return 0xFE835;
- case 0xE224:
- return 0xFE836;
- case 0xE225:
- return 0xFE837;
- case 0xE226:
- return 0xFEB3D;
- case 0xE227:
- return 0xFEB3E;
- case 0xE228:
- return 0xFEB3F;
- case 0xE229:
- return 0xFEB81;
- case 0xE22A:
- return 0xFEB31;
- case 0xE22B:
- return 0xFEB2F;
- case 0xE22C:
- return 0xFEB40;
- case 0xE22D:
- return 0xFEB41;
- case 0xE22E:
- return 0xFEB99;
- case 0xE22F:
- return 0xFEB9A;
- case 0xE230:
- return 0xFEB9B;
- case 0xE231:
- return 0xFEB9C;
- case 0xE232:
- return 0xFEAF8;
- case 0xE233:
- return 0xFEAF9;
- case 0xE234:
- return 0xFEAFA;
- case 0xE235:
- return 0xFEAFB;
- case 0xE236:
- return 0xFEAF0;
- case 0xE237:
- return 0xFEAF2;
- case 0xE238:
- return 0xFEAF1;
- case 0xE239:
- return 0xFEAF3;
- case 0xE23A:
- return 0xFEAFC;
- case 0xE23B:
- return 0xFEAFD;
- case 0xE23C:
- return 0xFEAFE;
- case 0xE23D:
- return 0xFEAFF;
- case 0xE23E:
- return 0xFE4F8;
- case 0xE23F:
- return 0xFE02B;
- case 0xE240:
- return 0xFE02C;
- case 0xE241:
- return 0xFE02D;
- case 0xE242:
- return 0xFE02E;
- case 0xE243:
- return 0xFE02F;
- case 0xE244:
- return 0xFE030;
- case 0xE245:
- return 0xFE031;
- case 0xE246:
- return 0xFE032;
- case 0xE247:
- return 0xFE033;
- case 0xE248:
- return 0xFE034;
- case 0xE249:
- return 0xFE035;
- case 0xE24A:
- return 0xFE036;
- case 0xE24B:
- return 0xFE037;
- case 0xE24C:
- return 0xFEB42;
- case 0xE24D:
- return 0xFEB27;
- case 0xE24E:
- return 0xFEB29;
- case 0xE24F:
- return 0xFEB2D;
- case 0xE250:
- return 0xFE839;
- case 0xE251:
- return 0xFE83A;
- case 0xE252:
- return 0xFEB23;
- case 0xE253:
- return 0xFE1B4;
- case 0xE254:
- return 0xFEE77;
- case 0xE255:
- return 0xFEE78;
- case 0xE256:
- return 0xFEE79;
- case 0xE257:
- return 0xFEE7A;
- case 0xE258:
- return 0xFEE7B;
- case 0xE259:
- return 0xFEE7C;
- case 0xE25A:
- return 0xFEE7D;
- case 0xE301:
- return 0xFE527;
- case 0xE302:
- return 0xFE4D3;
- case 0xE303:
- return 0xFE045;
- case 0xE304:
- return 0xFE03D;
- case 0xE305:
- return 0xFE046;
- case 0xE306:
- return 0xFE828;
- case 0xE307:
- return 0xFE047;
- case 0xE308:
- return 0xFE048;
- case 0xE309:
- return 0xFE508;
- case 0xE30A:
- return 0xFE803;
- case 0xE30B:
- return 0xFE985;
- case 0xE30C:
- return 0xFE987;
- case 0xE30D:
- return 0xFEB43;
- case 0xE30E:
- return 0xFEB1E;
- case 0xE30F:
- return 0xFE50A;
- case 0xE310:
- return 0xFE516;
- case 0xE311:
- return 0xFEB58;
- case 0xE312:
- return 0xFE517;
- case 0xE313:
- return 0xFE53E;
- case 0xE314:
- return 0xFE50F;
- case 0xE315:
- return 0xFEB2B;
- case 0xE316:
- return 0xFE53C;
- case 0xE317:
- return 0xFE530;
- case 0xE318:
- return 0xFE4D4;
- case 0xE319:
- return 0xFE4D5;
- case 0xE31A:
- return 0xFE4D7;
- case 0xE31B:
- return 0xFE4D8;
- case 0xE31C:
- return 0xFE195;
- case 0xE31D:
- return 0xFE196;
- case 0xE31E:
- return 0xFE197;
- case 0xE31F:
- return 0xFE198;
- case 0xE320:
- return 0xFE199;
- case 0xE321:
- return 0xFE4D9;
- case 0xE322:
- return 0xFE4DA;
- case 0xE323:
- return 0xFE4F0;
- case 0xE324:
- return 0xFE808;
- case 0xE325:
- return 0xFE4F2;
- case 0xE326:
- return 0xFE814;
- case 0xE327:
- return 0xFEB0D;
- case 0xE328:
- return 0xFEB11;
- case 0xE329:
- return 0xFEB12;
- case 0xE32A:
- return 0xFEB13;
- case 0xE32B:
- return 0xFEB14;
- case 0xE32C:
- return 0xFEB15;
- case 0xE32D:
- return 0xFEB16;
- case 0xE32E:
- return 0xFEB60;
- case 0xE32F:
- return 0xFEB68;
- case 0xE330:
- return 0xFEB5D;
- case 0xE331:
- return 0xFEB5B;
- case 0xE332:
- return 0xFEB44;
- case 0xE333:
- return 0xFEB45;
- case 0xE334:
- return 0xFEB57;
- case 0xE335:
- return 0xFEB69;
- case 0xE336:
- return 0xFEB0A;
- case 0xE337:
- return 0xFEB0B;
- case 0xE338:
- return 0xFE984;
- case 0xE339:
- return 0xFE964;
- case 0xE33A:
- return 0xFE966;
- case 0xE33B:
- return 0xFE967;
- case 0xE33C:
- return 0xFE968;
- case 0xE33D:
- return 0xFE969;
- case 0xE33E:
- return 0xFE96A;
- case 0xE33F:
- return 0xFE96B;
- case 0xE340:
- return 0xFE963;
- case 0xE341:
- return 0xFE96C;
- case 0xE342:
- return 0xFE961;
- case 0xE343:
- return 0xFE96D;
- case 0xE344:
- return 0xFE96E;
- case 0xE345:
- return 0xFE051;
- case 0xE346:
- return 0xFE052;
- case 0xE347:
- return 0xFE053;
- case 0xE348:
- return 0xFE054;
- case 0xE349:
- return 0xFE055;
- case 0xE34A:
- return 0xFE056;
- case 0xE34B:
- return 0xFE511;
- case 0xE34C:
- return 0xFE96F;
- case 0xE34D:
- return 0xFE970;
- case 0xE401:
- return 0xFE345;
- case 0xE402:
- return 0xFE343;
- case 0xE403:
- return 0xFE340;
- case 0xE404:
- return 0xFE333;
- case 0xE405:
- return 0xFE347;
- case 0xE406:
- return 0xFE33C;
- case 0xE407:
- return 0xFE33F;
- case 0xE408:
- return 0xFE342;
- case 0xE409:
- return 0xFE32A;
- case 0xE40A:
- return 0xFE33E;
- case 0xE40B:
- return 0xFE33B;
- case 0xE40C:
- return 0xFE32E;
- case 0xE40D:
- return 0xFE32F;
- case 0xE40E:
- return 0xFE326;
- case 0xE40F:
- return 0xFE325;
- case 0xE410:
- return 0xFE322;
- case 0xE411:
- return 0xFE33A;
- case 0xE412:
- return 0xFE334;
- case 0xE413:
- return 0xFE339;
- case 0xE414:
- return 0xFE336;
- case 0xE415:
- return 0xFE338;
- case 0xE416:
- return 0xFE33D;
- case 0xE417:
- return 0xFE32D;
- case 0xE418:
- return 0xFE32C;
- case 0xE419:
- return 0xFE190;
- case 0xE41A:
- return 0xFE192;
- case 0xE41B:
- return 0xFE191;
- case 0xE41C:
- return 0xFE193;
- case 0xE41D:
- return 0xFE35B;
- case 0xE41E:
- return 0xFEB9D;
- case 0xE41F:
- return 0xFEB9E;
- case 0xE420:
- return 0xFEB9F;
- case 0xE421:
- return 0xFEBA0;
- case 0xE422:
- return 0xFEBA1;
- case 0xE423:
- return 0xFE351;
- case 0xE424:
- return 0xFE352;
- case 0xE425:
- return 0xFE829;
- case 0xE426:
- return 0xFE353;
- case 0xE427:
- return 0xFE358;
- case 0xE428:
- return 0xFE1A0;
- case 0xE429:
- return 0xFE1A2;
- case 0xE42A:
- return 0xFE7D6;
- case 0xE42B:
- return 0xFE7DD;
- case 0xE42C:
- return 0xFE80E;
- case 0xE42D:
- return 0xFE7DE;
- case 0xE42E:
- return 0xFE7E5;
- case 0xE42F:
- return 0xFE7F1;
- case 0xE430:
- return 0xFE7F2;
- case 0xE431:
- return 0xFE7F3;
- case 0xE432:
- return 0xFE7F4;
- case 0xE433:
- return 0xFE7FE;
- case 0xE434:
- return 0xFE7E0;
- case 0xE435:
- return 0xFE7E2;
- case 0xE436:
- return 0xFE518;
- case 0xE437:
- return 0xFEB17;
- case 0xE438:
- return 0xFE519;
- case 0xE439:
- return 0xFE51A;
- case 0xE43A:
- return 0xFE51B;
- case 0xE43B:
- return 0xFE51C;
- case 0xE43C:
- return 0xFE007;
- case 0xE43D:
- return 0xFE82A;
- case 0xE43E:
- return 0xFE038;
- case 0xE43F:
- return 0xFE971;
- case 0xE440:
- return 0xFE51D;
- case 0xE441:
- return 0xFE1C6;
- case 0xE442:
- return 0xFE51E;
- case 0xE443:
- return 0xFE005;
- case 0xE444:
- return 0xFE049;
- case 0xE445:
- return 0xFE51F;
- case 0xE446:
- return 0xFE017;
- case 0xE447:
- return 0xFE043;
- case 0xE448:
- return 0xFE513;
- case 0xE449:
- return 0xFE00A;
- case 0xE44A:
- return 0xFE00C;
- case 0xE44B:
- return 0xFE008;
- case 0xE44C:
- return 0xFE00D;
- case 0xE501:
- return 0xFE4B8;
- case 0xE502:
- return 0xFE804;
- case 0xE503:
- return 0xFE805;
- case 0xE504:
- return 0xFE4BD;
- case 0xE505:
- return 0xFE4BE;
- case 0xE506:
- return 0xFE4BF;
- case 0xE507:
- return 0xFE802;
- case 0xE508:
- return 0xFE4C0;
- case 0xE509:
- return 0xFE4C4;
- case 0xE50A:
- return 0xFE4C5;
- case 0xE50B:
- return 0xFE4E5;
- case 0xE50C:
- return 0xFE4E6;
- case 0xE50D:
- return 0xFE4E7;
- case 0xE50E:
- return 0xFE4E8;
- case 0xE50F:
- return 0xFE4E9;
- case 0xE510:
- return 0xFE4EA;
- case 0xE511:
- return 0xFE4EB;
- case 0xE512:
- return 0xFE4EC;
- case 0xE513:
- return 0xFE4ED;
- case 0xE514:
- return 0xFE4EE;
- case 0xE515:
- return 0xFE1A4;
- case 0xE516:
- return 0xFE1A5;
- case 0xE517:
- return 0xFE1A6;
- case 0xE518:
- return 0xFE1A7;
- case 0xE519:
- return 0xFE1A8;
- case 0xE51A:
- return 0xFE1A9;
- case 0xE51B:
- return 0xFE1AA;
- case 0xE51C:
- return 0xFE1AB;
- case 0xE51D:
- return 0xFE4C6;
- case 0xE51E:
- return 0xFE1B5;
- case 0xE51F:
- return 0xFE1B6;
- case 0xE520:
- return 0xFE1C7;
- case 0xE521:
- return 0xFE1C8;
- case 0xE522:
- return 0xFE1C9;
- case 0xE523:
- return 0xFE1BA;
- case 0xE524:
- return 0xFE1CA;
- case 0xE525:
- return 0xFE1CB;
- case 0xE526:
- return 0xFE1CC;
- case 0xE527:
- return 0xFE1CD;
- case 0xE528:
- return 0xFE1CE;
- case 0xE529:
- return 0xFE1CF;
- case 0xE52A:
- return 0xFE1D0;
- case 0xE52B:
- return 0xFE1D1;
- case 0xE52C:
- return 0xFE1D2;
- case 0xE52D:
- return 0xFE1D3;
- case 0xE52E:
- return 0xFE1D4;
- case 0xE52F:
- return 0xFE1D5;
- case 0xE530:
- return 0xFE1D6;
- case 0xE531:
- return 0xFE1D7;
- case 0xE532:
- return 0xFE50B;
- case 0xE533:
- return 0xFE50C;
- case 0xE534:
- return 0xFE50D;
- case 0xE535:
- return 0xFE50E;
- case 0xE536:
- return 0xFE553;
- case 0xE537:
- return 0xFEB2A;
- case 0xE538:
- return 0xFEE70;
- case 0xE539:
- return 0xFEE71;
- case 0xE53A:
- return 0xFEE72;
- case 0xE53B:
- return 0xFEE73;
- case 0xE53C:
- return 0xFEE74;
- case 0xE53D:
- return 0xFEE75;
- case 0xE53E:
- return 0xFEE76;
- default:
- return codePoint;
- }
- }
-
- public static void setCharset(String charset, Part part) throws MessagingException {
- part.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
- part.getMimeType() + ";\r\n charset=" + getExternalCharset(charset));
- }
-
- public static String getExternalCharset(String charset) {
- if (charset.length() > 17 && charset.startsWith("x-") &&
- charset.endsWith("-shift_jis-2007"))
- return "shift_jis";
-
- return charset;
- }
-
- public static ViewableContainer extractPartsFromDraft(Message message)
- throws MessagingException {
-
- Body body = message.getBody();
- if (message.isMimeType("multipart/mixed") && body instanceof MimeMultipart) {
- MimeMultipart multipart = (MimeMultipart) body;
-
- ViewableContainer container;
- int count = multipart.getCount();
- if (count >= 1) {
- // The first part is either a text/plain or a multipart/alternative
- BodyPart firstPart = multipart.getBodyPart(0);
- container = extractTextual(firstPart);
-
- // The rest should be attachments
- for (int i = 1; i < count; i++) {
- BodyPart bodyPart = multipart.getBodyPart(i);
- container.attachments.add(bodyPart);
- }
- } else {
- container = new ViewableContainer("", "", new ArrayList());
- }
-
- return container;
- }
-
- return extractTextual(message);
- }
-
- private static ViewableContainer extractTextual(Part part) throws MessagingException {
- String text = "";
- String html = "";
- List attachments = new ArrayList();
-
- Body firstBody = part.getBody();
- if (part.isMimeType("text/plain")) {
- String bodyText = getTextFromPart(part);
- if (bodyText != null) {
- text = bodyText;
- html = HtmlConverter.textToHtml(text);
- }
- } else if (part.isMimeType("multipart/alternative") &&
- firstBody instanceof MimeMultipart) {
- MimeMultipart multipart = (MimeMultipart) firstBody;
- for (BodyPart bodyPart : multipart.getBodyParts()) {
- String bodyText = getTextFromPart(bodyPart);
- if (bodyText != null) {
- if (text.isEmpty() && bodyPart.isMimeType("text/plain")) {
- text = bodyText;
- } else if (html.isEmpty() && bodyPart.isMimeType("text/html")) {
- html = bodyText;
- }
- }
- }
- }
-
- return new ViewableContainer(text, html, attachments);
- }
}
diff --git a/src/com/fsck/k9/mail/internet/TextBody.java b/src/com/fsck/k9/mail/internet/TextBody.java
index ad6e474ac..d6355662f 100644
--- a/src/com/fsck/k9/mail/internet/TextBody.java
+++ b/src/com/fsck/k9/mail/internet/TextBody.java
@@ -4,7 +4,11 @@ package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
-import java.io.*;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
import org.apache.james.mime4j.util.MimeUtil;
diff --git a/src/com/fsck/k9/mail/internet/Viewable.java b/src/com/fsck/k9/mail/internet/Viewable.java
new file mode 100644
index 000000000..ec21b7c71
--- /dev/null
+++ b/src/com/fsck/k9/mail/internet/Viewable.java
@@ -0,0 +1,104 @@
+package com.fsck.k9.mail.internet;
+
+import com.fsck.k9.mail.Message;
+import com.fsck.k9.mail.Part;
+
+import java.util.List;
+
+/**
+ * Empty marker class interface the class hierarchy used by
+ * {@link MessageExtractor#getViewables(com.fsck.k9.mail.Part, java.util.List)}
+ *
+ * @see Viewable.Text
+ * @see Viewable.Html
+ * @see Viewable.MessageHeader
+ * @see Viewable.Alternative
+ */
+public interface Viewable {
+ /**
+ * Class representing textual parts of a message that aren't marked as attachments.
+ *
+ * @see com.fsck.k9.mail.internet.MessageExtractor#isPartTextualBody(com.fsck.k9.mail.Part)
+ */
+ abstract class Textual implements Viewable {
+ private Part mPart;
+
+ public Textual(Part part) {
+ mPart = part;
+ }
+
+ public Part getPart() {
+ return mPart;
+ }
+ }
+
+ /**
+ * Class representing a {@code text/plain} part of a message.
+ */
+ class Text extends Textual {
+ public Text(Part part) {
+ super(part);
+ }
+ }
+
+ /**
+ * Class representing a {@code text/html} part of a message.
+ */
+ class Html extends Textual {
+ public Html(Part part) {
+ super(part);
+ }
+ }
+
+ /**
+ * Class representing a {@code message/rfc822} part of a message.
+ *
+ *
+ * This is used to extract basic header information when the message contents are displayed
+ * inline.
+ *
+ */
+ class MessageHeader implements Viewable {
+ private Part mContainerPart;
+ private Message mMessage;
+
+ public MessageHeader(Part containerPart, Message message) {
+ mContainerPart = containerPart;
+ mMessage = message;
+ }
+
+ public Part getContainerPart() {
+ return mContainerPart;
+ }
+
+ public Message getMessage() {
+ return mMessage;
+ }
+ }
+
+ /**
+ * Class representing a {@code multipart/alternative} part of a message.
+ *
+ *
+ * Only relevant {@code text/plain} and {@code text/html} children are stored in this container
+ * class.
+ *
+ */
+ class Alternative implements Viewable {
+ private List mText;
+ private List mHtml;
+
+ public Alternative(List text, List html) {
+ mText = text;
+ mHtml = html;
+ }
+
+ public List getText() {
+ return mText;
+ }
+
+ public List getHtml() {
+ return mHtml;
+ }
+ }
+}
diff --git a/src/com/fsck/k9/net/ssl/KeyChainKeyManager.java b/src/com/fsck/k9/mail/ssl/KeyChainKeyManager.java
similarity index 95%
rename from src/com/fsck/k9/net/ssl/KeyChainKeyManager.java
rename to src/com/fsck/k9/mail/ssl/KeyChainKeyManager.java
index c15fc58ad..273b7dd9a 100644
--- a/src/com/fsck/k9/net/ssl/KeyChainKeyManager.java
+++ b/src/com/fsck/k9/mail/ssl/KeyChainKeyManager.java
@@ -1,5 +1,5 @@
-package com.fsck.k9.net.ssl;
+package com.fsck.k9.mail.ssl;
import java.net.Socket;
import java.security.Principal;
@@ -20,16 +20,17 @@ import android.security.KeyChain;
import android.security.KeyChainException;
import android.util.Log;
-import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.MessagingException;
+import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
+
/**
* For client certificate authentication! Provide private keys and certificates
* during the TLS handshake using the Android 4.0 KeyChain API.
*/
-public class KeyChainKeyManager extends X509ExtendedKeyManager {
+class KeyChainKeyManager extends X509ExtendedKeyManager {
private static PrivateKey sClientCertificateReferenceWorkaround;
@@ -207,10 +208,10 @@ public class KeyChainKeyManager extends X509ExtendedKeyManager {
return mAlias;
}
}
- Log.w(K9.LOG_TAG, "Client certificate " + mAlias + " not issued by any of the requested issuers");
+ Log.w(LOG_TAG, "Client certificate " + mAlias + " not issued by any of the requested issuers");
return null;
}
- Log.w(K9.LOG_TAG, "Client certificate " + mAlias + " does not match any of the requested key types");
+ Log.w(LOG_TAG, "Client certificate " + mAlias + " does not match any of the requested key types");
return null;
}
}
diff --git a/src/com/fsck/k9/security/LocalKeyStore.java b/src/com/fsck/k9/mail/ssl/LocalKeyStore.java
similarity index 95%
rename from src/com/fsck/k9/security/LocalKeyStore.java
rename to src/com/fsck/k9/mail/ssl/LocalKeyStore.java
index d432ab294..cc0587c2c 100644
--- a/src/com/fsck/k9/security/LocalKeyStore.java
+++ b/src/com/fsck/k9/mail/ssl/LocalKeyStore.java
@@ -1,4 +1,4 @@
-package com.fsck.k9.security;
+package com.fsck.k9.mail.ssl;
import java.io.File;
import java.io.FileInputStream;
@@ -15,7 +15,7 @@ import org.apache.commons.io.IOUtils;
import android.util.Log;
-import com.fsck.k9.K9;
+import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public class LocalKeyStore {
private static final int KEY_STORE_FILE_VERSION = 1;
@@ -50,7 +50,7 @@ public class LocalKeyStore {
* error, presuming setKeyStoreFile(File) is called next with a
* non-null File.
*/
- Log.w(K9.LOG_TAG, "Local key store has not been initialized");
+ Log.w(LOG_TAG, "Local key store has not been initialized");
}
}
@@ -92,7 +92,7 @@ public class LocalKeyStore {
mKeyStore = store;
mKeyStoreFile = file;
} catch (Exception e) {
- Log.e(K9.LOG_TAG, "Failed to initialize local key store", e);
+ Log.e(LOG_TAG, "Failed to initialize local key store", e);
// Use of the local key store is effectively disabled.
mKeyStore = null;
mKeyStoreFile = null;
@@ -169,7 +169,7 @@ public class LocalKeyStore {
} catch (KeyStoreException e) {
// Ignore: most likely there was no cert. found
} catch (CertificateException e) {
- Log.e(K9.LOG_TAG, "Error updating the local key store file", e);
+ Log.e(LOG_TAG, "Error updating the local key store file", e);
}
}
diff --git a/src/com/fsck/k9/net/ssl/TrustManagerFactory.java b/src/com/fsck/k9/mail/ssl/TrustManagerFactory.java
similarity index 98%
rename from src/com/fsck/k9/net/ssl/TrustManagerFactory.java
rename to src/com/fsck/k9/mail/ssl/TrustManagerFactory.java
index 4e20f5c67..f6841a2e3 100644
--- a/src/com/fsck/k9/net/ssl/TrustManagerFactory.java
+++ b/src/com/fsck/k9/mail/ssl/TrustManagerFactory.java
@@ -1,10 +1,9 @@
-package com.fsck.k9.net.ssl;
+package com.fsck.k9.mail.ssl;
import android.util.Log;
import com.fsck.k9.mail.CertificateChainException;
-import com.fsck.k9.security.LocalKeyStore;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
diff --git a/src/com/fsck/k9/net/ssl/TrustedSocketFactory.java b/src/com/fsck/k9/mail/ssl/TrustedSocketFactory.java
similarity index 97%
rename from src/com/fsck/k9/net/ssl/TrustedSocketFactory.java
rename to src/com/fsck/k9/mail/ssl/TrustedSocketFactory.java
index 12188f957..cf74bb899 100644
--- a/src/com/fsck/k9/net/ssl/TrustedSocketFactory.java
+++ b/src/com/fsck/k9/mail/ssl/TrustedSocketFactory.java
@@ -1,4 +1,4 @@
-package com.fsck.k9.net.ssl;
+package com.fsck.k9.mail.ssl;
import android.util.Log;
@@ -19,6 +19,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
+
/**
* Filter and reorder list of cipher suites and TLS versions.
@@ -90,7 +92,7 @@ public class TrustedSocketFactory {
*/
supportedProtocols = sock.getSupportedProtocols();
} catch (Exception e) {
- Log.e(K9.LOG_TAG, "Error getting information about available SSL/TLS ciphers and " +
+ Log.e(LOG_TAG, "Error getting information about available SSL/TLS ciphers and " +
"protocols", e);
}
diff --git a/src/com/fsck/k9/mail/store/ImapResponseParser.java b/src/com/fsck/k9/mail/store/ImapResponseParser.java
index 4a8c8cc2e..b0e61fb70 100644
--- a/src/com/fsck/k9/mail/store/ImapResponseParser.java
+++ b/src/com/fsck/k9/mail/store/ImapResponseParser.java
@@ -11,7 +11,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
-public class ImapResponseParser {
+class ImapResponseParser {
private static final SimpleDateFormat mDateTimeFormat = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
private static final SimpleDateFormat badDateTimeFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss Z", Locale.US);
private static final SimpleDateFormat badDateTimeFormat2 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss Z", Locale.US);
@@ -293,7 +293,7 @@ public class ImapResponseParser {
} catch (Exception e) {
// Catch everything else and save it for later.
mException = e;
- //Log.e(K9.LOG_TAG, "parseLiteral(): Exception in callback method", e);
+ //Log.e(LOG_TAG, "parseLiteral(): Exception in callback method", e);
}
// Check if only some of the literal data was read
diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java
index afd4384fd..ae54c870b 100644
--- a/src/com/fsck/k9/mail/store/ImapStore.java
+++ b/src/com/fsck/k9/mail/store/ImapStore.java
@@ -55,12 +55,8 @@ import android.os.PowerManager;
import android.text.TextUtils;
import android.util.Log;
-import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
-import com.fsck.k9.controller.MessageRetrievalListener;
-import com.fsck.k9.helper.UrlEncodingHelper;
-import com.fsck.k9.helper.Utility;
import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
import com.fsck.k9.mail.AuthType;
@@ -72,13 +68,14 @@ import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
+import com.fsck.k9.mail.K9MailLib;
import com.fsck.k9.mail.Message;
+import com.fsck.k9.mail.MessageRetrievalListener;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.Pusher;
import com.fsck.k9.mail.ServerSettings;
-import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.filter.FixedLengthInputStream;
@@ -90,22 +87,25 @@ import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.ImapResponseParser.ImapList;
import com.fsck.k9.mail.store.ImapResponseParser.ImapResponse;
-import com.fsck.k9.mail.store.imap.ImapUtility;
import com.fsck.k9.mail.transport.imap.ImapSettings;
-import com.fsck.k9.net.ssl.TrustedSocketFactory;
+import com.fsck.k9.mail.ssl.TrustedSocketFactory;
import com.beetstra.jutf7.CharsetProvider;
import com.jcraft.jzlib.JZlib;
import com.jcraft.jzlib.ZOutputStream;
import org.apache.commons.io.IOUtils;
+import static com.fsck.k9.mail.K9MailLib.DEBUG_PROTOCOL_IMAP;
+import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
+import static com.fsck.k9.mail.K9MailLib.PUSH_WAKE_LOCK_TIMEOUT;
+
/**
*