Block POP3 senders

pull/208/head
M66B 2 years ago
parent 66293e7a74
commit 47bae9a8e2

@ -2032,25 +2032,6 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
DB db = DB.getInstance(context);
EntityMessage message = db.message().getMessage(id);
if (message != null) {
List<Address> senders = new ArrayList<>();
if (message.from != null)
senders.addAll(Arrays.asList(message.from));
if (message.reply != null)
senders.addAll(Arrays.asList(message.reply));
List<TupleIdentityEx> identities = db.identity().getComposableIdentities(null);
if (identities != null) {
for (TupleIdentityEx identity : identities)
for (Address sender : senders)
if (identity.self && identity.similarAddress(sender)) {
data.fromSelf = true;
break;
}
}
}
EntityAccount account = db.account().getAccount(aid);
data.isGmail = (account != null && account.isGmail());
data.folders = db.folder().getSystemFolders(aid);
@ -2182,7 +2163,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ibMove.setVisibility(tools && button_move && move ? View.VISIBLE : View.GONE);
ibArchive.setVisibility(tools && button_archive && archive ? View.VISIBLE : View.GONE);
ibTrash.setVisibility(outbox || (tools && button_trash && trash) ? View.VISIBLE : View.GONE);
ibJunk.setVisibility(tools && button_junk && report && !data.fromSelf ? View.VISIBLE : View.GONE);
ibJunk.setVisibility(tools && button_junk && report ? View.VISIBLE : View.GONE);
ibInbox.setVisibility(tools && inbox ? View.VISIBLE : View.GONE);
ibMore.setVisibility(tools && !outbox ? View.VISIBLE : View.GONE);
ibTools.setImageLevel(tools ? 0 : 1);
@ -5362,18 +5343,28 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}
private void onActionJunk(TupleMessageEx message) {
Bundle aargs = new Bundle();
aargs.putLong("id", message.id);
aargs.putLong("account", message.account);
aargs.putInt("protocol", message.accountProtocol);
aargs.putLong("folder", message.folder);
aargs.putString("type", message.folderType);
aargs.putString("from", DB.Converters.encodeAddresses(message.from));
if (message.accountProtocol == EntityAccount.TYPE_POP) {
Bundle aargs = new Bundle();
aargs.putLongArray("ids", new long[]{message.id});
FragmentDialogJunk ask = new FragmentDialogJunk();
ask.setArguments(aargs);
ask.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_JUNK);
ask.show(parentFragment.getParentFragmentManager(), "message:junk");
FragmentDialogBlockSender ask = new FragmentDialogBlockSender();
ask.setArguments(aargs);
ask.setTargetFragment(parentFragment, FragmentMessages.REQUEST_BLOCK_SENDERS);
ask.show(parentFragment.getParentFragmentManager(), "message:block");
} else {
Bundle aargs = new Bundle();
aargs.putLong("id", message.id);
aargs.putLong("account", message.account);
aargs.putInt("protocol", message.accountProtocol);
aargs.putLong("folder", message.folder);
aargs.putString("type", message.folderType);
aargs.putString("from", DB.Converters.encodeAddresses(message.from));
FragmentDialogJunk ask = new FragmentDialogJunk();
ask.setArguments(aargs);
ask.setTargetFragment(parentFragment, FragmentMessages.REQUEST_MESSAGE_JUNK);
ask.show(parentFragment.getParentFragmentManager(), "message:junk");
}
}
private void onActionInbox(TupleMessageEx message) {
@ -6817,7 +6808,6 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
};
private class ToolData {
private boolean fromSelf;
private boolean isGmail;
private List<EntityFolder> folders;
private List<EntityAttachment> attachments;

@ -267,6 +267,22 @@ public class EntityMessage implements Serializable {
return (this.plain_only != null && (this.plain_only & 0x80) != 0);
}
boolean fromSelf(List<TupleIdentityEx> identities) {
List<Address> senders = new ArrayList<>();
if (from != null)
senders.addAll(Arrays.asList(from));
if (reply != null)
senders.addAll(Arrays.asList(reply));
if (identities != null)
for (TupleIdentityEx identity : identities)
for (Address sender : senders)
if (identity.self && identity.similarAddress(sender))
return true;
return false;
}
boolean replySelf(List<TupleIdentityEx> identities, long account) {
Address[] senders = (reply == null || reply.length == 0 ? from : reply);
if (identities != null && senders != null)

@ -0,0 +1,71 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2022 by Marcel Bokhorst (M66B)
*/
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import java.text.NumberFormat;
public class FragmentDialogBlockSender extends FragmentDialogBase {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
long[] ids = getArguments().getLongArray("ids");
final Context context = getContext();
final View view = LayoutInflater.from(context).inflate(R.layout.dialog_block_sender, null);
final TextView tvMessage = view.findViewById(R.id.tvMessage);
final TextView tvJunkHint = view.findViewById(R.id.tvJunkHint);
tvJunkHint.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 92);
}
});
NumberFormat NF = NumberFormat.getNumberInstance();
String msg = context.getResources().getQuantityString(R.plurals.title_ask_block_sender,
ids.length, NF.format(ids.length));
tvMessage.setText(msg);
return new AlertDialog.Builder(context)
.setView(view)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendResult(Activity.RESULT_OK);
}
})
.create();
}
}

@ -77,13 +77,10 @@ public class FragmentDialogJunk extends FragmentDialogBase {
final String type = args.getString("type");
final Address[] froms = DB.Converters.decodeAddresses(args.getString("from"));
boolean imap = (protocol == EntityAccount.TYPE_IMAP);
final Context context = getContext();
final View view = LayoutInflater.from(context).inflate(R.layout.dialog_junk, null);
final TextView tvMessage = view.findViewById(R.id.tvMessage);
final ImageButton ibInfoProvider = view.findViewById(R.id.ibInfoProvider);
final TextView tvPopHint = view.findViewById(R.id.tvPopHint);
final TextView tvJunkHint = view.findViewById(R.id.tvJunkHint);
final CheckBox cbBlockSender = view.findViewById(R.id.cbBlockSender);
final CheckBox cbBlockDomain = view.findViewById(R.id.cbBlockDomain);
final ImageButton ibMore = view.findViewById(R.id.ibMore);
@ -113,15 +110,13 @@ public class FragmentDialogJunk extends FragmentDialogBase {
// Wire controls
ibInfoProvider.setOnClickListener(new View.OnClickListener() {
tvJunkHint.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 92);
}
});
tvPopHint.setVisibility(imap ? View.GONE : View.VISIBLE);
cbBlockSender.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@ -138,7 +133,7 @@ public class FragmentDialogJunk extends FragmentDialogBase {
grpManage.setVisibility(View.GONE);
} else {
ibMore.setImageLevel(0);
grpFilter.setVisibility(imap ? View.VISIBLE : View.GONE);
grpFilter.setVisibility(View.VISIBLE);
grpManage.setVisibility(View.VISIBLE);
}
}
@ -387,8 +382,8 @@ public class FragmentDialogJunk extends FragmentDialogBase {
cbBlocklist.setChecked(check_blocklist && use_blocklist);
tvBlocklist.setText(TextUtils.join(", ", DnsBlockList.getNamesEnabled(context)));
cbBlockSender.setVisibility(imap ? View.VISIBLE : View.GONE);
grpBlockDomain.setVisibility(domains.size() > 0 && imap ? View.VISIBLE : View.GONE);
cbBlockSender.setVisibility(View.VISIBLE);
grpBlockDomain.setVisibility(domains.size() > 0 ? View.VISIBLE : View.GONE);
grpFilter.setVisibility(View.GONE);
grpManage.setVisibility(View.GONE);
@ -442,8 +437,8 @@ public class FragmentDialogJunk extends FragmentDialogBase {
@Override
public void onClick(DialogInterface dialog, int which) {
prefs.edit().putBoolean("block_sender", cbBlockSender.isChecked()).apply();
getArguments().putBoolean("block_sender", cbBlockSender.isChecked() || !imap);
getArguments().putBoolean("block_domain", cbBlockDomain.isChecked() && imap);
getArguments().putBoolean("block_sender", cbBlockSender.isChecked());
getArguments().putBoolean("block_domain", cbBlockDomain.isChecked());
sendResult(Activity.RESULT_OK);
}
});

@ -425,6 +425,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
private static final int REQUEST_SAVE_SEARCH = 26;
private static final int REQUEST_DELETE_SEARCH = 27;
private static final int REQUEST_QUICK_ACTIONS = 28;
static final int REQUEST_BLOCK_SENDERS = 29;
static final String ACTION_STORE_RAW = BuildConfig.APPLICATION_ID + ".STORE_RAW";
static final String ACTION_DECRYPT = BuildConfig.APPLICATION_ID + ".DECRYPT";
@ -1378,7 +1379,14 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
ibJunk.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onActionJunkSelection();
MoreResult result = (MoreResult) cardMore.getTag();
if (result == null)
return;
if (result.hasPop && !result.hasImap)
onActionBlockSender();
else if (!result.hasPop && result.hasImap)
onActionJunkSelection();
}
});
@ -2896,18 +2904,28 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
}
private void onSwipeJunk(final @NonNull TupleMessageEx message) {
Bundle aargs = new Bundle();
aargs.putLong("id", message.id);
aargs.putLong("account", message.account);
aargs.putInt("protocol", message.accountProtocol);
aargs.putLong("folder", message.folder);
aargs.putString("type", message.folderType);
aargs.putString("from", DB.Converters.encodeAddresses(message.from));
FragmentDialogJunk ask = new FragmentDialogJunk();
ask.setArguments(aargs);
ask.setTargetFragment(FragmentMessages.this, REQUEST_MESSAGE_JUNK);
ask.show(getParentFragmentManager(), "swipe:junk");
if (message.accountProtocol == EntityAccount.TYPE_POP) {
Bundle aargs = new Bundle();
aargs.putLongArray("ids", new long[]{message.id});
FragmentDialogBlockSender ask = new FragmentDialogBlockSender();
ask.setArguments(aargs);
ask.setTargetFragment(FragmentMessages.this, REQUEST_BLOCK_SENDERS);
ask.show(getParentFragmentManager(), "message:block");
} else {
Bundle aargs = new Bundle();
aargs.putLong("id", message.id);
aargs.putLong("account", message.account);
aargs.putInt("protocol", message.accountProtocol);
aargs.putLong("folder", message.folder);
aargs.putString("type", message.folderType);
aargs.putString("from", DB.Converters.encodeAddresses(message.from));
FragmentDialogJunk ask = new FragmentDialogJunk();
ask.setArguments(aargs);
ask.setTargetFragment(FragmentMessages.this, REQUEST_MESSAGE_JUNK);
ask.show(getParentFragmentManager(), "swipe:junk");
}
}
private void onSwipeDelete(@NonNull TupleMessageEx message, int pos) {
@ -3528,7 +3546,10 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
onActionMoveSelection(EntityFolder.ARCHIVE, false);
return true;
} else if (itemId == R.string.title_spam) {
onActionJunkSelection();
if (result.hasPop && !result.hasImap)
onActionBlockSender();
else if (!result.hasPop && result.hasImap)
onActionJunkSelection();
return true;
} else if (itemId == R.string.title_trash) {
onActionMoveSelection(EntityFolder.TRASH, false);
@ -3926,6 +3947,16 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
ask.show(getParentFragmentManager(), "messages:junk");
}
private void onActionBlockSender() {
Bundle args = new Bundle();
args.putLongArray("ids", getSelection());
FragmentDialogBlockSender ask = new FragmentDialogBlockSender();
ask.setArguments(args);
ask.setTargetFragment(FragmentMessages.this, REQUEST_BLOCK_SENDERS);
ask.show(getParentFragmentManager(), "messages:block");
}
private void onActionMoveSelection(final String type, boolean block) {
Bundle args = new Bundle();
args.putString("type", type);
@ -7584,6 +7615,10 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
if (resultCode == RESULT_OK)
updateMore();
break;
case REQUEST_BLOCK_SENDERS:
if (resultCode == RESULT_OK)
onBlockSenders(data.getBundleExtra("args"));
break;
}
} catch (Throwable ex) {
Log.e(ex);
@ -8840,8 +8875,12 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
if (message == null)
return null;
List<TupleIdentityEx> identities = db.identity().getComposableIdentities(null);
if (message.fromSelf(identities))
return null;
EntityAccount account = db.account().getAccount(message.account);
if (account == null)
if (account == null || account.protocol != EntityAccount.TYPE_IMAP)
return null;
if (block_sender)
@ -8849,28 +8888,25 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
message.account, message.identity, message.from,
EntityContact.TYPE_JUNK, message.received);
if (account.protocol == EntityAccount.TYPE_IMAP) {
EntityFolder junk = db.folder().getFolderByType(message.account, EntityFolder.JUNK);
if (junk == null)
throw new IllegalArgumentException(context.getString(R.string.title_no_junk_folder));
if (!message.folder.equals(junk.id))
EntityOperation.queue(context, message, EntityOperation.MOVE, junk.id, null, null, true);
if (block_domain) {
List<EntityRule> rules = EntityRule.blockSender(context, message, junk, block_domain);
for (EntityRule rule : rules) {
if (message.folder.equals(junk.id)) {
EntityFolder inbox = db.folder().getFolderByType(message.account, EntityFolder.INBOX);
if (inbox == null)
continue;
rule.folder = inbox.id;
}
rule.id = db.rule().insertRule(rule);
EntityFolder junk = db.folder().getFolderByType(message.account, EntityFolder.JUNK);
if (junk == null)
throw new IllegalArgumentException(context.getString(R.string.title_no_junk_folder));
if (!message.folder.equals(junk.id))
EntityOperation.queue(context, message, EntityOperation.MOVE, junk.id, null, null, true);
if (block_domain) {
List<EntityRule> rules = EntityRule.blockSender(context, message, junk, block_domain);
for (EntityRule rule : rules) {
if (message.folder.equals(junk.id)) {
EntityFolder inbox = db.folder().getFolderByType(message.account, EntityFolder.INBOX);
if (inbox == null)
continue;
rule.folder = inbox.id;
}
rule.id = db.rule().insertRule(rule);
}
} else
db.message().deleteMessage(message.id);
}
db.setTransactionSuccessful();
} finally {
@ -8902,6 +8938,49 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
}.execute(this, args, "message:junk");
}
private void onBlockSenders(Bundle args) {
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) throws Throwable {
long[] ids = args.getLongArray("ids");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
List<TupleIdentityEx> identities = db.identity().getComposableIdentities(null);
for (long id : ids) {
EntityMessage message = db.message().getMessage(id);
if (message == null || message.fromSelf(identities))
continue;
EntityAccount account = db.account().getAccount(message.account);
if (account == null || account.protocol != EntityAccount.TYPE_POP)
continue;
EntityContact.update(context,
message.account, message.identity, message.from,
EntityContact.TYPE_JUNK, message.received);
db.message().deleteMessage(message.id);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "messages:block");
}
private void onMoveAskAcross(final ArrayList<MessageTarget> result) {
boolean across = false;
for (MessageTarget target : result)
@ -9665,7 +9744,8 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
boolean canJunk() {
if (read_only)
return false;
return (hasJunk && !isJunk && !isDrafts);
return (hasJunk && !isJunk && !isDrafts) ||
(hasPop && !hasImap);
}
boolean canTrash() {

@ -955,6 +955,11 @@ public class FragmentPop extends FragmentBase {
hide.name = getString(R.string.title_hide);
folders.add(hide);
EntityFolder junk = new EntityFolder();
junk.id = EntityMessage.SWIPE_ACTION_JUNK;
junk.name = getString(R.string.title_report_spam);
folders.add(junk);
EntityFolder delete = new EntityFolder();
delete.id = EntityMessage.SWIPE_ACTION_DELETE;
delete.name = getString(R.string.title_delete_permanently);

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<eu.faircode.email.ScrollViewEx xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
android:scrollbarStyle="outsideOverlay">
<eu.faircode.email.ConstraintLayoutEx
android:layout_width="match_parent"
android:layout_height="wrap_content">
<eu.faircode.email.FixedTextView
android:id="@+id/tvMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableStart="@drawable/twotone_report_24"
android:drawablePadding="6dp"
android:drawableTint="?attr/colorWarning"
android:text="Block sender of n messages?"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<eu.faircode.email.FixedTextView
android:id="@+id/tvJunkHint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:drawableEnd="@drawable/twotone_info_24"
android:drawablePadding="6dp"
android:drawableTint="?attr/colorAccent"
android:minHeight="45dp"
android:text="@string/title_junk_hint"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="italic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvMessage" />
<eu.faircode.email.FixedTextView
android:id="@+id/tvPopHint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_junk_pop_hint"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvJunkHint" />
</eu.faircode.email.ConstraintLayoutEx>
</eu.faircode.email.ScrollViewEx>

@ -27,39 +27,16 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:drawableEnd="@drawable/twotone_info_24"
android:drawablePadding="6dp"
android:drawableTint="?attr/colorAccent"
android:minHeight="45dp"
android:text="@string/title_junk_hint"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="italic"
app:layout_constraintEnd_toStartOf="@+id/ibInfoProvider"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvMessage" />
<eu.faircode.email.FixedImageButton
android:id="@+id/ibInfoProvider"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/title_info"
android:padding="6dp"
android:scaleType="fitCenter"
android:tooltipText="@string/title_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvJunkHint"
app:srcCompat="@drawable/twotone_info_24"
app:tint="?attr/colorAccent" />
<eu.faircode.email.FixedTextView
android:id="@+id/tvPopHint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_junk_pop_hint"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@+id/ibInfoProvider"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvJunkHint" />
app:layout_constraintTop_toBottomOf="@+id/tvMessage" />
<CheckBox
android:id="@+id/cbBlockSender"
@ -69,7 +46,7 @@
android:text="@string/title_block_sender"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvPopHint" />
app:layout_constraintTop_toBottomOf="@id/tvJunkHint" />
<CheckBox
android:id="@+id/cbBlockDomain"

@ -105,6 +105,11 @@
</string>
<string name="title_blocked_senders">Blocked senders</string>
<plurals name="title_ask_block_sender">
<item quantity="one">Block sender of %1$s message?</item>
<item quantity="other">Block sender of %1$s messages?</item>
</plurals>
<string name="title_junk_filter">Use local spam filter</string>
<string name="title_junk_filter_hint">This can increase battery usage and incorrectly mark messages as spam</string>
<string name="title_junk_blocklist">Use spam block lists</string>

Loading…
Cancel
Save