1
0
mirror of https://github.com/moparisthebest/k-9 synced 2025-01-10 05:08:18 -05:00

Merge branch 'master' into issue-162

This commit is contained in:
ashley willis 2012-02-03 21:10:22 -06:00
commit 14a0fdf27d
8 changed files with 249 additions and 123 deletions

View File

@ -1033,6 +1033,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin
<string name="save_or_discard_draft_message_dlg_title">Save draft message?</string> <string name="save_or_discard_draft_message_dlg_title">Save draft message?</string>
<string name="save_or_discard_draft_message_instructions_fmt">Save or Discard this message?</string> <string name="save_or_discard_draft_message_instructions_fmt">Save or Discard this message?</string>
<string name="confirm_discard_draft_message_title">Discard message?</string>
<string name="confirm_discard_draft_message">Are you sure you want to discard this message?</string>
<string name="refuse_to_save_draft_marked_encrypted_dlg_title">Refuse to save draft message.</string> <string name="refuse_to_save_draft_marked_encrypted_dlg_title">Refuse to save draft message.</string>
<string name="refuse_to_save_draft_marked_encrypted_instructions_fmt">Refuse to save draft message marked encrypted.</string> <string name="refuse_to_save_draft_marked_encrypted_instructions_fmt">Refuse to save draft message marked encrypted.</string>

View File

@ -170,6 +170,10 @@
<incoming uri="pop3://incoming.verizon.net" username="$user" /> <incoming uri="pop3://incoming.verizon.net" username="$user" />
<outgoing uri="smtp://outgoing.verizon.net" username="$user" /> <outgoing uri="smtp://outgoing.verizon.net" username="$user" />
</provider> </provider>
<provider id="montclair.edu" label="MSU" domain="montclair.edu">
<incoming uri="imap+ssl+://mail.montclair.edu" username="$user" />
<outgoing uri="smtp+tls+://smtp.montclair.edu" username="$user" />
</provider>
<!-- Yahoo! Mail Variants --> <!-- Yahoo! Mail Variants -->
<provider id="yahoo" label="Yahoo" domain="yahoo.com"> <provider id="yahoo" label="Yahoo" domain="yahoo.com">

View File

@ -83,6 +83,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
private static final int DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE = 1; private static final int DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE = 1;
private static final int DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED = 2; private static final int DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED = 2;
private static final int DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY = 3; private static final int DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY = 3;
private static final int DIALOG_CONFIRM_DISCARD_ON_BACK = 4;
private static final long INVALID_DRAFT_ID = MessagingController.INVALID_MESSAGE_ID; private static final long INVALID_DRAFT_ID = MessagingController.INVALID_MESSAGE_ID;
@ -2051,13 +2052,21 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (mEncryptCheckbox.isChecked()) { if (mDraftNeedsSaving) {
showDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED); if (mEncryptCheckbox.isChecked()) {
} else if (!mDraftNeedsSaving || isDraftsFolderDisabled()) { showDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED);
Toast.makeText(MessageCompose.this, getString(R.string.message_discarded_toast), Toast.LENGTH_LONG).show(); } else if (isDraftsFolderDisabled()) {
super.onBackPressed(); showDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
} else {
showDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
}
} else { } else {
showDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE); // Check if editing an existing draft.
if (mDraftId == INVALID_DRAFT_ID) {
onDiscard();
} else {
super.onBackPressed();
}
} }
} }
@ -2118,6 +2127,27 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} }
}) })
.create(); .create();
case DIALOG_CONFIRM_DISCARD_ON_BACK:
return new AlertDialog.Builder(this)
.setTitle(R.string.confirm_discard_draft_message_title)
.setMessage(R.string.confirm_discard_draft_message)
.setPositiveButton(R.string.cancel_action, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
}
})
.setNegativeButton(R.string.discard_action, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
Toast.makeText(MessageCompose.this,
getString(R.string.message_discarded_toast),
Toast.LENGTH_LONG).show();
onDiscard();
}
})
.create();
} }
return super.onCreateDialog(id); return super.onCreateDialog(id);
} }
@ -2988,9 +3018,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
} }
MessagingController.getInstance(getApplication()).sendMessage(mAccount, message, null); MessagingController.getInstance(getApplication()).sendMessage(mAccount, message, null);
if (mDraftId != INVALID_DRAFT_ID) { long draftId = mDraftId;
MessagingController.getInstance(getApplication()).deleteDraft(mAccount, mDraftId); if (draftId != INVALID_DRAFT_ID) {
mDraftId = INVALID_DRAFT_ID; mDraftId = INVALID_DRAFT_ID;
MessagingController.getInstance(getApplication()).deleteDraft(mAccount, draftId);
} }
return null; return null;

View File

@ -20,6 +20,7 @@ import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.helper.FileBrowserHelper; import com.fsck.k9.helper.FileBrowserHelper;
import com.fsck.k9.helper.FileBrowserHelper.FileBrowserFailOverCallback; import com.fsck.k9.helper.FileBrowserHelper.FileBrowserFailOverCallback;
import com.fsck.k9.mail.*; import com.fsck.k9.mail.*;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.StorageManager; import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.view.AttachmentView; import com.fsck.k9.view.AttachmentView;
import com.fsck.k9.view.ToggleScrollView; import com.fsck.k9.view.ToggleScrollView;
@ -712,10 +713,15 @@ public class MessageView extends K9Activity implements OnClickListener {
private void onFlag() { private void onFlag() {
if (mMessage != null) { if (mMessage != null) {
mController.setFlag(mAccount, boolean newState = !mMessage.isSet(Flag.FLAGGED);
mMessage.getFolder().getName(), new String[] {mMessage.getUid()}, Flag.FLAGGED, !mMessage.isSet(Flag.FLAGGED)); mController.setFlag(mAccount, mMessage.getFolder().getName(),
new String[] {mMessage.getUid()}, Flag.FLAGGED, newState);
try { try {
mMessage.setFlag(Flag.FLAGGED, !mMessage.isSet(Flag.FLAGGED)); // FIXME: This is a hack to change the flagged state of our message object. We
// can't call Message.setFlag() because that would "adjust" the flagged count
// another time (first time by MessagingController.setFlag(...)).
((LocalMessage)mMessage).setFlagInternal(Flag.FLAGGED, newState);
mMessageView.setHeaders(mMessage, mAccount); mMessageView.setHeaders(mMessage, mAccount);
} catch (MessagingException me) { } catch (MessagingException me) {
Log.e(K9.LOG_TAG, "Could not set flag on local message", me); Log.e(K9.LOG_TAG, "Could not set flag on local message", me);
@ -848,9 +854,13 @@ public class MessageView extends K9Activity implements OnClickListener {
private void onMarkAsUnread() { private void onMarkAsUnread() {
if (mMessage != null) { if (mMessage != null) {
// (Issue 3319) mController.setFlag(mAccount, mMessageReference.folderName, new String[] { mMessage.getUid() }, Flag.SEEN, false); mController.setFlag(mAccount, mMessage.getFolder().getName(),
new String[] { mMessage.getUid() }, Flag.SEEN, false);
try { try {
mMessage.setFlag(Flag.SEEN, false); // FIXME: This is a hack to mark our message object as unread. We can't call
// Message.setFlag() because that would "adjust" the unread count twice.
((LocalMessage)mMessage).setFlagInternal(Flag.SEEN, false);
mMessageView.setHeaders(mMessage, mAccount); mMessageView.setHeaders(mMessage, mAccount);
String subject = mMessage.getSubject(); String subject = mMessage.getSubject();
setTitle(subject); setTitle(subject);

View File

@ -3371,9 +3371,11 @@ public class MessagingController implements Runnable {
localFolder = localStore.getFolder(account.getDraftsFolderName()); localFolder = localStore.getFolder(account.getDraftsFolderName());
localFolder.open(OpenMode.READ_WRITE); localFolder.open(OpenMode.READ_WRITE);
String uid = localFolder.getMessageUidById(id); String uid = localFolder.getMessageUidById(id);
Message message = localFolder.getMessage(uid); if (uid != null) {
if (message != null) { Message message = localFolder.getMessage(uid);
deleteMessages(new Message[] { message }, null); if (message != null) {
deleteMessages(new Message[] { message }, null);
}
} }
} catch (MessagingException me) { } catch (MessagingException me) {
addErrorMessage(account, null, me); addErrorMessage(account, null, me);
@ -3528,12 +3530,13 @@ public class MessagingController implements Runnable {
putBackground("emptyTrash", listener, new Runnable() { putBackground("emptyTrash", listener, new Runnable() {
@Override @Override
public void run() { public void run() {
Folder localFolder = null; LocalFolder localFolder = null;
try { try {
Store localStore = account.getLocalStore(); Store localStore = account.getLocalStore();
localFolder = localStore.getFolder(account.getTrashFolderName()); localFolder = (LocalFolder) localStore.getFolder(account.getTrashFolderName());
localFolder.open(OpenMode.READ_WRITE); localFolder.open(OpenMode.READ_WRITE);
localFolder.setFlags(new Flag[] { Flag.DELETED }, true); localFolder.setFlags(new Flag[] { Flag.DELETED }, true);
localFolder.resetUnreadAndFlaggedCounts();
for (MessagingListener l : getListeners()) { for (MessagingListener l : getListeners()) {
l.emptyTrashCompleted(account); l.emptyTrashCompleted(account);

View File

@ -2725,7 +2725,7 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly);
setVisibleLimit(mAccount.getDisplayCount()); setVisibleLimit(mAccount.getDisplayCount());
} }
private void resetUnreadAndFlaggedCounts() { public void resetUnreadAndFlaggedCounts() {
try { try {
int newUnread = 0; int newUnread = 0;
int newFlagged = 0; int newFlagged = 0;
@ -2799,22 +2799,34 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly);
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException { public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
Cursor attachmentsCursor = null; Cursor attachmentsCursor = null;
try { try {
attachmentsCursor = db.query("attachments", new String[] String accountUuid = mAccount.getUuid();
{ "id" }, "message_id = ?", new String[] Context context = mApplication;
{ Long.toString(messageId) }, null, null, null);
// Get attachment IDs
String[] whereArgs = new String[] { Long.toString(messageId) };
attachmentsCursor = db.query("attachments", new String[] { "id" },
"message_id = ?", whereArgs, null, null, null);
final File attachmentDirectory = StorageManager.getInstance(mApplication) final File attachmentDirectory = StorageManager.getInstance(mApplication)
.getAttachmentDirectory(uUid, database.getStorageProviderId()); .getAttachmentDirectory(uUid, database.getStorageProviderId());
while (attachmentsCursor.moveToNext()) { while (attachmentsCursor.moveToNext()) {
long attachmentId = attachmentsCursor.getLong(0); String attachmentId = Long.toString(attachmentsCursor.getLong(0));
try { try {
File file = new File(attachmentDirectory, Long.toString(attachmentId)); // Delete stored attachment
File file = new File(attachmentDirectory, attachmentId);
if (file.exists()) { if (file.exists()) {
file.delete(); file.delete();
} }
} catch (Exception e) {
} // Delete thumbnail file
AttachmentProvider.deleteThumbnail(context, accountUuid,
attachmentId);
} catch (Exception e) { /* ignore */ }
} }
// Delete attachment metadata from the database
db.delete("attachments", "message_id = ?", whereArgs);
} finally { } finally {
Utility.closeQuietly(attachmentsCursor); Utility.closeQuietly(attachmentsCursor);
} }

View File

@ -23,8 +23,12 @@ import java.io.*;
import java.util.List; import java.util.List;
/** /**
* A simple ContentProvider that allows file access to Email's attachments.<br/> * A simple ContentProvider that allows file access to attachments.
* Warning! We make heavy assumptions about the Uris used by the {@link LocalStore} for an {@link Account} here. *
* <p>
* Warning! We make heavy assumptions about the Uris used by the {@link LocalStore} for an
* {@link Account} here.
* </p>
*/ */
public class AttachmentProvider extends ContentProvider { public class AttachmentProvider extends ContentProvider {
public static final Uri CONTENT_URI = Uri.parse("content://com.fsck.k9.attachmentprovider"); public static final Uri CONTENT_URI = Uri.parse("content://com.fsck.k9.attachmentprovider");
@ -33,6 +37,11 @@ public class AttachmentProvider extends ContentProvider {
private static final String FORMAT_VIEW = "VIEW"; private static final String FORMAT_VIEW = "VIEW";
private static final String FORMAT_THUMBNAIL = "THUMBNAIL"; private static final String FORMAT_THUMBNAIL = "THUMBNAIL";
private static final String[] DEFAULT_PROJECTION = new String[] {
AttachmentProviderColumns._ID,
AttachmentProviderColumns.DATA,
};
public static class AttachmentProviderColumns { public static class AttachmentProviderColumns {
public static final String _ID = "_id"; public static final String _ID = "_id";
public static final String DATA = "_data"; public static final String DATA = "_data";
@ -40,6 +49,7 @@ public class AttachmentProvider extends ContentProvider {
public static final String SIZE = "_size"; public static final String SIZE = "_size";
} }
public static Uri getAttachmentUri(Account account, long id) { public static Uri getAttachmentUri(Account account, long id) {
return getAttachmentUri(account.getUuid(), id, true); return getAttachmentUri(account.getUuid(), id, true);
} }
@ -66,6 +76,47 @@ public class AttachmentProvider extends ContentProvider {
.build(); .build();
} }
public static void clear(Context context) {
/*
* We use the cache dir as a temporary directory (since Android doesn't give us one) so
* on startup we'll clean up any .tmp files from the last run.
*/
File[] files = context.getCacheDir().listFiles();
for (File file : files) {
try {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Deleting file " + file.getCanonicalPath());
}
} catch (IOException ioe) { /* No need to log failure to log */ }
file.delete();
}
}
/**
* Delete the thumbnail of an attachment.
*
* @param context
* The application context.
* @param accountUuid
* The UUID of the account the attachment belongs to.
* @param attachmentId
* The ID of the attachment the thumbnail was created for.
*/
public static void deleteThumbnail(Context context, String accountUuid, String attachmentId) {
File file = getThumbnailFile(context, accountUuid, attachmentId);
if (file.exists()) {
file.delete();
}
}
private static File getThumbnailFile(Context context, String accountUuid,
String attachmentId) {
String filename = "thmb_" + accountUuid + "_" + attachmentId + ".tmp";
File dir = context.getCacheDir();
return new File(dir, filename);
}
@Override @Override
public boolean onCreate() { public boolean onCreate() {
/* /*
@ -89,21 +140,6 @@ public class AttachmentProvider extends ContentProvider {
return true; return true;
} }
public static void clear(Context lContext) {
/*
* We use the cache dir as a temporary directory (since Android doesn't give us one) so
* on startup we'll clean up any .tmp files from the last run.
*/
File[] files = lContext.getCacheDir().listFiles();
for (File file : files) {
try {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Deleting file " + file.getCanonicalPath());
} catch (IOException ioe) {} // No need to log failure to log
file.delete();
}
}
@Override @Override
public String getType(Uri uri) { public String getType(Uri uri) {
List<String> segments = uri.getPathSegments(); List<String> segments = uri.getPathSegments();
@ -114,67 +150,24 @@ public class AttachmentProvider extends ContentProvider {
return getType(dbName, id, format); return getType(dbName, id, format);
} }
private String getType(String dbName, String id, String format) {
if (FORMAT_THUMBNAIL.equals(format)) {
return "image/png";
} else {
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
try {
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id);
if (FORMAT_VIEW.equals(format)) {
return MimeUtility.getMimeTypeForViewing(attachmentInfo.type, attachmentInfo.name);
} else {
// When accessing the "raw" message we deliver the original MIME type.
return attachmentInfo.type;
}
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Unable to retrieve LocalStore for " + account, e);
return null;
}
}
}
private File getFile(String dbName, String id)
throws FileNotFoundException {
try {
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
final File attachmentsDir;
attachmentsDir = StorageManager.getInstance(K9.app).getAttachmentDirectory(dbName,
account.getLocalStorageProviderId());
final File file = new File(attachmentsDir, id);
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath());
}
return file;
} catch (IOException e) {
Log.w(K9.LOG_TAG, null, e);
throw new FileNotFoundException(e.getMessage());
}
}
@Override @Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File file;
List<String> segments = uri.getPathSegments(); List<String> segments = uri.getPathSegments();
String dbName = segments.get(0); // "/sdcard/..." is URL-encoded and makes up only 1 segment String accountUuid = segments.get(0);
String id = segments.get(1); String attachmentId = segments.get(1);
String format = segments.get(2); String format = segments.get(2);
if (FORMAT_THUMBNAIL.equals(format)) { if (FORMAT_THUMBNAIL.equals(format)) {
int width = Integer.parseInt(segments.get(3)); int width = Integer.parseInt(segments.get(3));
int height = Integer.parseInt(segments.get(4)); int height = Integer.parseInt(segments.get(4));
String filename = "thmb_" + dbName + "_" + id + ".tmp";
int index = dbName.lastIndexOf('/'); file = getThumbnailFile(getContext(), accountUuid, attachmentId);
if (index >= 0) {
filename = /*dbName.substring(0, index + 1) + */"thmb_" + dbName.substring(index + 1) + "_" + id + ".tmp";
}
File dir = getContext().getCacheDir();
File file = new File(dir, filename);
if (!file.exists()) { if (!file.exists()) {
String type = getType(dbName, id, FORMAT_VIEW); String type = getType(accountUuid, attachmentId, FORMAT_VIEW);
try { try {
FileInputStream in = new FileInputStream(getFile(dbName, id)); FileInputStream in = new FileInputStream(getFile(accountUuid, attachmentId));
try { try {
Bitmap thumbnail = createThumbnail(type, in); Bitmap thumbnail = createThumbnail(type, in);
if (thumbnail != null) { if (thumbnail != null) {
@ -187,40 +180,24 @@ public class AttachmentProvider extends ContentProvider {
} }
} }
} finally { } finally {
try { in.close(); } catch (Throwable ignore) {} try { in.close(); } catch (Throwable ignore) { /* ignore */ }
} }
} catch (IOException ioe) { } catch (IOException ioe) {
return null; return null;
} }
} }
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
} else { } else {
return ParcelFileDescriptor.open( file = getFile(accountUuid, attachmentId);
getFile(dbName, id),
ParcelFileDescriptor.MODE_READ_ONLY);
} }
}
@Override return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
public int delete(Uri uri, String arg1, String[] arg2) {
return 0;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
} }
@Override @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) { String sortOrder) {
if (projection == null) {
projection = String[] columnNames = (projection == null) ? DEFAULT_PROJECTION : projection;
new String[] {
AttachmentProviderColumns._ID,
AttachmentProviderColumns.DATA,
};
}
List<String> segments = uri.getPathSegments(); List<String> segments = uri.getPathSegments();
String dbName = segments.get(0); String dbName = segments.get(0);
@ -232,7 +209,6 @@ public class AttachmentProvider extends ContentProvider {
dbName = dbName.substring(0, dbName.length() - 3); dbName = dbName.substring(0, dbName.length() - 3);
} }
//String format = segments.get(2);
final AttachmentInfo attachmentInfo; final AttachmentInfo attachmentInfo;
try { try {
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName); final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
@ -242,10 +218,10 @@ public class AttachmentProvider extends ContentProvider {
return null; return null;
} }
MatrixCursor ret = new MatrixCursor(projection); MatrixCursor ret = new MatrixCursor(columnNames);
Object[] values = new Object[projection.length]; Object[] values = new Object[columnNames.length];
for (int i = 0, count = projection.length; i < count; i++) { for (int i = 0, count = columnNames.length; i < count; i++) {
String column = projection[i]; String column = columnNames[i];
if (AttachmentProviderColumns._ID.equals(column)) { if (AttachmentProviderColumns._ID.equals(column)) {
values[i] = id; values[i] = id;
} else if (AttachmentProviderColumns.DATA.equals(column)) { } else if (AttachmentProviderColumns.DATA.equals(column)) {
@ -265,6 +241,56 @@ public class AttachmentProvider extends ContentProvider {
return 0; return 0;
} }
@Override
public int delete(Uri uri, String arg1, String[] arg2) {
return 0;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
private String getType(String dbName, String id, String format) {
String type;
if (FORMAT_THUMBNAIL.equals(format)) {
type = "image/png";
} else {
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
try {
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id);
if (FORMAT_VIEW.equals(format)) {
type = MimeUtility.getMimeTypeForViewing(attachmentInfo.type, attachmentInfo.name);
} else {
// When accessing the "raw" message we deliver the original MIME type.
type = attachmentInfo.type;
}
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Unable to retrieve LocalStore for " + account, e);
type = null;
}
}
return type;
}
private File getFile(String dbName, String id) throws FileNotFoundException {
Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
File attachmentsDir = StorageManager.getInstance(K9.app).getAttachmentDirectory(dbName,
account.getLocalStorageProviderId());
File file = new File(attachmentsDir, id);
if (!file.exists()) {
throw new FileNotFoundException(file.getAbsolutePath());
}
return file;
}
private Bitmap createThumbnail(String type, InputStream data) { private Bitmap createThumbnail(String type, InputStream data) {
if (MimeUtility.mimeTypeMatches(type, "image/*")) { if (MimeUtility.mimeTypeMatches(type, "image/*")) {
return createImageThumbnail(data); return createImageThumbnail(data);

View File

@ -39,6 +39,26 @@ import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart;
import com.fsck.k9.provider.AttachmentProvider; import com.fsck.k9.provider.AttachmentProvider;
public class AttachmentView extends FrameLayout { public class AttachmentView extends FrameLayout {
/**
* Regular expression that represents characters we won't allow in file names.
*
* <p>
* Allowed are:
* <ul>
* <li>word characters (letters, digits, and underscores): {@code \w}</li>
* <li>spaces: {@code " "}</li>
* <li>special characters: {@code !}, {@code #}, {@code $}, {@code %}, {@code &}, {@code '},
* {@code (}, {@code )}, {@code -}, {@code @}, {@code ^}, {@code `}, <code>&#123;</code>,
* <code>&#125;</code>, {@code ~}, {@code .}, {@code ,}</li>
* </ul></p>
*/
private static final String INVALID_CHARACTERS = "[^\\w !#$%&'()\\-@\\^`{}~.,]+";
/**
* Invalid characters in a file name are replaced by this character.
*/
private static final String REPLACEMENT_CHARACTER = "_";
private Context mContext; private Context mContext;
public Button viewButton; public Button viewButton;
@ -196,7 +216,8 @@ public class AttachmentView extends FrameLayout {
*/ */
public void writeFile(File directory) { public void writeFile(File directory) {
try { try {
File file = Utility.createUniqueFile(directory, name); String filename = sanitizeFilename(name);
File file = Utility.createUniqueFile(directory, filename);
Uri uri = AttachmentProvider.getAttachmentUri(mAccount, part.getAttachmentId()); Uri uri = AttachmentProvider.getAttachmentUri(mAccount, part.getAttachmentId());
InputStream in = mContext.getContentResolver().openInputStream(uri); InputStream in = mContext.getContentResolver().openInputStream(uri);
OutputStream out = new FileOutputStream(file); OutputStream out = new FileOutputStream(file);
@ -207,9 +228,25 @@ public class AttachmentView extends FrameLayout {
attachmentSaved(file.toString()); attachmentSaved(file.toString());
new MediaScannerNotifier(mContext, file); new MediaScannerNotifier(mContext, file);
} catch (IOException ioe) { } catch (IOException ioe) {
if (K9.DEBUG) {
Log.e(K9.LOG_TAG, "Error saving attachment", ioe);
}
attachmentNotSaved(); attachmentNotSaved();
} }
} }
/**
* Replace characters we don't allow in file names with a replacement character.
*
* @param filename
* The original file name.
*
* @return The sanitized file name containing only allowed characters.
*/
private String sanitizeFilename(String filename) {
return filename.replaceAll(INVALID_CHARACTERS, REPLACEMENT_CHARACTER);
}
/** /**
* saves the file to the defaultpath setting in the config, or if the config * saves the file to the defaultpath setting in the config, or if the config
* is not set => to the Environment * is not set => to the Environment