Update file type acceptance rules

* Open up all types for view/save rules
* Add anti-malware block list (based on extensions)
* Clean up code that shows/hides view & save buttons
* Redo handling of load/cancel 1sec timer
* Unit test for new little utility
* Allow larger items when on wifi

Bug: 3338984
Bug: 3334950
Bug: 3338988
Bug: 3340835

Change-Id: I991135636d507f2660e2860720dbed21bd1a955b
This commit is contained in:
Andy Stadler 2011-01-11 14:40:41 -08:00
parent 0faa37d771
commit 86753bc41c
4 changed files with 128 additions and 29 deletions

View File

@ -112,7 +112,7 @@ public class Email extends Application {
* The MIME type(s) of attachments we're willing to download to SD.
*/
public static final String[] ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] {
"image/*",
"*/*",
};
/**
@ -121,6 +121,21 @@ public class Email extends Application {
public static final String[] UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] {
};
/**
* Filename extensions of attachments we're never willing to download (potential malware).
* Entries in this list are compared to the end of the lower-cased filename, so they must
* be lower case, and should not include a "."
*/
public static final String[] UNACCEPTABLE_ATTACHMENT_EXTENSIONS = new String[] {
// File types that contain malware
"ade", "adp", "bat", "chm", "cmd", "com", "cpl", "dll", "exe",
"hta", "ins", "isp", "jse", "lib", "mde", "msc", "msp",
"mst", "pif", "scr", "sct", "shb", "sys", "vb", "vbe",
"vbs", "vxd", "wsc", "wsf", "wsh",
// File types of common compression/container formats (again, to avoid malware)
"zip", "gz", "z", "tar", "tgz", "bz2",
};
/**
* Specifies how many messages will be shown in a folder by default. This number is set
* on each new folder and can be incremented with "Load more messages..." by the

View File

@ -49,6 +49,8 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@ -201,6 +203,9 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
/**
* Encapsulates known information about a single attachment.
*
* TODO: This should have methods to encapsulate the entire state graph of loading, canceling,
* viewing, and saving.
*/
private static class AttachmentInfo {
public String name;
@ -213,6 +218,9 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
public Button cancelButton;
public ImageView iconView;
public ProgressBar progressView;
public boolean allowView;
public boolean allowSave;
public boolean isLoaded;
}
public interface Callback {
@ -738,10 +746,8 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
}
@Override
protected void onPostExecute(Void result) {
// If the load buttons is shown, it's already loaded -- don't show the stop
// button.
if (attachment.cancelButton.getVisibility() != View.VISIBLE
&& attachment.loadButton.getVisibility() != View.VISIBLE) {
// If the timeout completes and the attachment has not loaded, show cancel
if (!attachment.isLoaded) {
attachment.cancelButton.setVisibility(View.VISIBLE);
}
}
@ -773,10 +779,15 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
private void doFinishLoadAttachment(long attachmentId) {
AttachmentInfo info = findAttachmentInfo(attachmentId);
if (info != null) {
info.isLoaded = true;
info.loadButton.setVisibility(View.GONE);
info.cancelButton.setVisibility(View.GONE);
info.saveButton.setVisibility(TextUtils.isEmpty(info.name) ? View.GONE : View.VISIBLE);
info.viewButton.setVisibility(View.VISIBLE);
boolean showSave = info.allowSave && !TextUtils.isEmpty(info.name);
boolean showView = info.allowView;
info.saveButton.setVisibility(showSave ? View.VISIBLE : View.GONE);
info.viewButton.setVisibility(showView ? View.VISIBLE : View.GONE);
}
}
@ -1108,6 +1119,9 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
AttachmentProvider.inferMimeType(attachment.mFileName, attachment.mMimeType);
attachmentInfo.name = attachment.mFileName;
attachmentInfo.attachmentId = attachment.mId;
attachmentInfo.allowView = true;
attachmentInfo.allowSave = true;
attachmentInfo.isLoaded = false;
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.message_view_attachment, null);
@ -1121,16 +1135,42 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
Button attachmentCancel = (Button)view.findViewById(R.id.cancel);
ProgressBar attachmentProgress = (ProgressBar)view.findViewById(R.id.progress);
// TODO: Remove this test (acceptable types = everything; unacceptable = nothing)
if ((!MimeUtility.mimeTypeMatches(attachmentInfo.contentType,
Email.ACCEPTABLE_ATTACHMENT_VIEW_TYPES))
|| (MimeUtility.mimeTypeMatches(attachmentInfo.contentType,
Email.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) {
attachmentView.setVisibility(View.GONE);
// Check for acceptable / unacceptable attachments by MIME-type
String contentType = attachmentInfo.contentType;
if ((!MimeUtility.mimeTypeMatches(contentType, Email.ACCEPTABLE_ATTACHMENT_VIEW_TYPES)) ||
(MimeUtility.mimeTypeMatches(contentType, Email.UNACCEPTABLE_ATTACHMENT_VIEW_TYPES))) {
attachmentInfo.allowView = false;
}
// Check for unacceptable attachments by filename extension; hide both buttons
String extension = AttachmentProvider.getFilenameExtension(attachmentInfo.name);
if (!TextUtils.isEmpty(extension) &&
Utility.arrayContains(Email.UNACCEPTABLE_ATTACHMENT_EXTENSIONS, extension)) {
attachmentInfo.allowView = false;
attachmentInfo.allowSave = false;
}
// File size exceeded; Hide both buttons
// The size limit is overridden when on a wifi connection - any size is OK
if (attachmentInfo.size > Email.MAX_ATTACHMENT_DOWNLOAD_SIZE) {
ConnectivityManager cm = (ConnectivityManager)
mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo network = cm.getActiveNetworkInfo();
if (network == null || network.getType() != ConnectivityManager.TYPE_WIFI) {
attachmentInfo.allowView = false;
attachmentInfo.allowSave = false;
}
}
// Don't enable the "save" button if we've got no place to save the file
if (!Utility.isExternalStorageMounted()) {
attachmentInfo.allowSave = false;
}
if (!attachmentInfo.allowView) {
attachmentView.setVisibility(View.GONE);
}
if (!attachmentInfo.allowSave) {
attachmentSave.setVisibility(View.GONE);
}
@ -1141,15 +1181,29 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
attachmentInfo.iconView = attachmentIcon;
attachmentInfo.progressView = attachmentProgress;
// If the attachment is loaded, show 100% progress
// Note that for POP3 messages, the user will only see "Open" and "Save" since the entire
// message is loaded before being shown.
if (Utility.attachmentExists(mContext, attachment)) {
if (!attachmentInfo.allowView && !attachmentInfo.allowSave) {
// This attachment may never be viewed or saved, so block everything
attachmentProgress.setVisibility(View.GONE);
attachmentView.setVisibility(View.GONE);
attachmentSave.setVisibility(View.GONE);
attachmentLoad.setVisibility(View.GONE);
attachmentCancel.setVisibility(View.GONE);
// TODO: Maybe show a little icon to denote blocked download
} else if (Utility.attachmentExists(mContext, attachment)) {
// If the attachment is loaded, show 100% progress
// Note that for POP3 messages, the user will only see "Open" and "Save",
// because the entire message is loaded before being shown.
attachmentInfo.isLoaded = true;
// Hide "Load", show "View" and "Save"
attachmentProgress.setVisibility(View.VISIBLE);
attachmentProgress.setProgress(100);
attachmentSave.setVisibility(View.VISIBLE);
attachmentView.setVisibility(View.VISIBLE);
if (attachmentInfo.allowSave) {
attachmentSave.setVisibility(View.VISIBLE);
}
if (attachmentInfo.allowView) {
attachmentView.setVisibility(View.VISIBLE);
}
attachmentLoad.setVisibility(View.GONE);
attachmentCancel.setVisibility(View.GONE);
@ -1158,6 +1212,8 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
attachmentIcon.setImageBitmap(previewIcon);
}
} else {
// The attachment is not loaded, so present UI to start downloading it
// Show "Load"; hide "View" and "Save"
attachmentSave.setVisibility(View.GONE);
attachmentView.setVisibility(View.GONE);
@ -1175,11 +1231,6 @@ public abstract class MessageViewFragmentBase extends Fragment implements View.O
}
}
// Don't enable the "save" button if we've got no place to save the file
if (!Utility.isExternalStorageMounted()) {
attachmentSave.setEnabled(false);
}
view.setTag(attachmentInfo);
attachmentView.setOnClickListener(this);
attachmentView.setTag(attachmentInfo);

View File

@ -208,11 +208,7 @@ public class AttachmentProvider extends ContentProvider {
// Try to find an extension in the filename
if (!TextUtils.isEmpty(fileName)) {
int lastDot = fileName.lastIndexOf('.');
String extension = null;
if ((lastDot > 0) && (lastDot < fileName.length() - 1)) {
extension = fileName.substring(lastDot + 1).toLowerCase();
}
String extension = getFilenameExtension(fileName);
if (!TextUtils.isEmpty(extension)) {
// Extension found. Look up mime type, or synthesize if none found.
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
@ -227,6 +223,22 @@ public class AttachmentProvider extends ContentProvider {
return "application/octet-stream";
}
/**
* Extract and return filename's extension, converted to lower case, and not including the "."
*
* @return extension, or null if not found (or null/empty filename)
*/
public static String getFilenameExtension(String fileName) {
String extension = null;
if (!TextUtils.isEmpty(fileName)) {
int lastDot = fileName.lastIndexOf('.');
if ((lastDot > 0) && (lastDot < fileName.length() - 1)) {
extension = fileName.substring(lastDot + 1).toLowerCase();
}
}
return extension;
}
/**
* Open an attachment file. There are two "modes" - "raw", which returns an actual file,
* and "thumbnail", which attempts to generate a thumbnail image.

View File

@ -312,6 +312,27 @@ public class AttachmentProviderTests extends ProviderTestCase2<AttachmentProvide
assertEquals("message/rfc822", AttachmentProvider.inferMimeType("a.eml", DEFAULT));
}
/**
* Text extension extractor
*/
public void testGetFilenameExtension() {
final String FILE_NO_EXTENSION = "myfile";
final String FILE_EXTENSION = "myfile.pDf";
final String FILE_TWO_EXTENSIONS = "myfile.false.AbC";
assertNull(AttachmentProvider.getFilenameExtension(null));
assertNull(AttachmentProvider.getFilenameExtension(""));
assertNull(AttachmentProvider.getFilenameExtension(FILE_NO_EXTENSION));
assertEquals("pdf", AttachmentProvider.getFilenameExtension(FILE_EXTENSION));
assertEquals("abc", AttachmentProvider.getFilenameExtension(FILE_TWO_EXTENSIONS));
// The API makes no claim as to how these are handled (it probably should),
// but make sure that they don't crash.
AttachmentProvider.getFilenameExtension("filename.");
AttachmentProvider.getFilenameExtension(".extension");
}
/**
* test openFile()
* - regular file