Added VirusTotal integration option

pull/209/head
M66B 2 years ago
parent 0b4aecdcd2
commit facaaf5a6a

@ -0,0 +1,33 @@
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.content.Context;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner;
public class Check {
static final String URI_PRIVACY = "";
static void virus(Context context, LifecycleOwner owner, FragmentManager fm, long id) {
// Do nothing
}
}

@ -0,0 +1,70 @@
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.content.Context;
import android.net.Uri;
import android.os.Bundle;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner;
import java.io.FileInputStream;
import java.io.InputStream;
public class Check {
static final String URI_PRIVACY = "https://support.virustotal.com/hc/en-us/articles/115002168385-Privacy-Policy";
private static final String URI_VIRUS_TOTAL = "https://www.virustotal.com/";
static void virus(Context context, LifecycleOwner owner, FragmentManager fm, long id) {
Bundle args = new Bundle();
args.putLong("id", id);
new SimpleTask<String>() {
@Override
protected String onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
DB db = DB.getInstance(context);
EntityAttachment attachment = db.attachment().getAttachment(id);
if (attachment == null)
return null;
try (InputStream is = new FileInputStream(attachment.getFile(context))) {
return Helper.getHash(is, "SHA-256");
}
}
@Override
protected void onExecuted(Bundle args, String hash) {
if (hash == null)
return;
Uri uri = Uri.parse(URI_VIRUS_TOTAL + "gui/file/" + hash);
Helper.view(context, uri, false);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(fm, ex);
}
}.execute(context, owner, args, "attachment:scan");
}
}

@ -48,11 +48,7 @@ import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListUpdateCallback;
import androidx.recyclerview.widget.RecyclerView;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -68,6 +64,7 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
private LayoutInflater inflater;
private boolean readonly;
private boolean vt_enabled;
private boolean debug;
private int dp12;
private int dp36;
@ -109,7 +106,8 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
view.setOnClickListener(this);
ibDelete.setOnClickListener(this);
ibSave.setOnClickListener(this);
ibScan.setOnClickListener(this);
if (vt_enabled)
ibScan.setOnClickListener(this);
view.setOnLongClickListener(this);
}
@ -117,7 +115,8 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
view.setOnClickListener(null);
ibDelete.setOnClickListener(null);
ibSave.setOnClickListener(null);
ibScan.setOnClickListener(null);
if (vt_enabled)
ibScan.setOnClickListener(null);
view.setOnLongClickListener(null);
}
@ -176,7 +175,7 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
}
ibSave.setVisibility(attachment.available ? View.VISIBLE : View.GONE);
ibScan.setVisibility(attachment.available && !BuildConfig.PLAY_STORE_RELEASE ? View.VISIBLE : View.GONE);
ibScan.setVisibility(attachment.available && vt_enabled ? View.VISIBLE : View.GONE);
if (attachment.progress != null)
progressbar.setProgress(attachment.progress);
@ -216,7 +215,7 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
else if (id == R.id.ibSave)
onSave(attachment);
else if (id == R.id.ibScan)
onScan(attachment);
Check.virus(context, owner, parentFragment.getParentFragmentManager(), attachment.id);
else {
if (attachment.available)
onShare(attachment);
@ -311,47 +310,6 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
.putExtra("type", attachment.getMimeType()));
}
private void onScan(EntityAttachment attachment) {
Bundle args = new Bundle();
args.putLong("id", attachment.id);
new SimpleTask<String>() {
@Override
protected String onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
DB db = DB.getInstance(context);
EntityAttachment attachment = db.attachment().getAttachment(id);
if (attachment == null)
return null;
MessageDigest digest = MessageDigest.getInstance("SHA-256");
try (InputStream is = new BufferedInputStream(new FileInputStream(attachment.getFile(context)))) {
int count;
byte[] buffer = new byte[1024];
while ((count = is.read(buffer)) != -1)
digest.update(buffer, 0, count);
}
return Helper.hex(digest.digest());
}
@Override
protected void onExecuted(Bundle args, String hash) {
if (hash == null)
return;
Uri uri = Uri.parse(Helper.URI_VIRUS_TOTAL + "gui/file/" + hash);
Helper.view(context, uri, false);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(parentFragment.getParentFragmentManager(), ex);
}
}.execute(context, owner, args, "attachment:scan");
}
private void onShare(EntityAttachment attachment) {
String title = (attachment.name == null ? attachment.cid : attachment.name);
Helper.share(context, attachment.getFile(context), attachment.getMimeType(), title);
@ -422,6 +380,7 @@ public class AdapterAttachment extends RecyclerView.Adapter<AdapterAttachment.Vi
this.inflater = LayoutInflater.from(context);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
this.vt_enabled = (prefs.getBoolean("vt_enabled", false) && !BuildConfig.PLAY_STORE_RELEASE);
this.debug = prefs.getBoolean("debug", false);
this.dp12 = Helper.dp2pixels(context, 12);
this.dp36 = Helper.dp2pixels(context, 36);

@ -118,7 +118,8 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private ImageButton ibLanguageTool;
private SwitchCompat swDeepL;
private ImageButton ibDeepL;
private TextView tvSdcard;
private SwitchCompat swVirusTotal;
private TextView tvVirusTotalPrivacy;
private SwitchCompat swUpdates;
private ImageButton ibChannelUpdated;
private SwitchCompat swCheckWeekly;
@ -129,6 +130,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private SwitchCompat swCleanupAttachments;
private Button btnCleanup;
private TextView tvLastCleanup;
private TextView tvSdcard;
private CardView cardAdvanced;
private SwitchCompat swWatchdog;
@ -206,6 +208,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private Button btnAllPermissions;
private TextView tvPermissions;
private Group grpVirusTotal;
private Group grpUpdates;
private Group grpTest;
private CardView cardDebug;
@ -217,7 +220,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private final static String[] RESET_OPTIONS = new String[]{
"sort_answers", "shortcuts", "fts",
"classification", "class_min_probability", "class_min_difference",
"language", "lt_enabled", "deepl_enabled",
"language", "lt_enabled", "deepl_enabled", "vt_enabled",
"updates", "weekly", "show_changelog",
"crash_reports", "cleanup_attachments",
"watchdog", "experiments", "main_log", "protocol", "log_level", "debug", "leak_canary", "test1",
@ -303,7 +306,8 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
ibLanguageTool = view.findViewById(R.id.ibLanguageTool);
swDeepL = view.findViewById(R.id.swDeepL);
ibDeepL = view.findViewById(R.id.ibDeepL);
tvSdcard = view.findViewById(R.id.tvSdcard);
swVirusTotal = view.findViewById(R.id.swVirusTotal);
tvVirusTotalPrivacy = view.findViewById(R.id.tvVirusTotalPrivacy);
swUpdates = view.findViewById(R.id.swUpdates);
ibChannelUpdated = view.findViewById(R.id.ibChannelUpdated);
swCheckWeekly = view.findViewById(R.id.swWeekly);
@ -314,6 +318,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
swCleanupAttachments = view.findViewById(R.id.swCleanupAttachments);
btnCleanup = view.findViewById(R.id.btnCleanup);
tvLastCleanup = view.findViewById(R.id.tvLastCleanup);
tvSdcard = view.findViewById(R.id.tvSdcard);
cardAdvanced = view.findViewById(R.id.cardAdvanced);
swWatchdog = view.findViewById(R.id.swWatchdog);
@ -391,6 +396,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
btnAllPermissions = view.findViewById(R.id.btnAllPermissions);
tvPermissions = view.findViewById(R.id.tvPermissions);
grpVirusTotal = view.findViewById(R.id.grpVirusTotal);
grpUpdates = view.findViewById(R.id.grpUpdates);
grpTest = view.findViewById(R.id.grpTest);
cardDebug = view.findViewById(R.id.cardDebug);
@ -623,11 +629,18 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
}
});
tvSdcard.setPaintFlags(tvSdcard.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
tvSdcard.setOnClickListener(new View.OnClickListener() {
swVirusTotal.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("vt_enabled", checked).apply();
}
});
tvVirusTotalPrivacy.getPaint().setUnderlineText(true);
tvVirusTotalPrivacy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 93);
Helper.view(v.getContext(), Uri.parse(Check.URI_PRIVACY), true);
}
});
@ -702,6 +715,14 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
}
});
tvSdcard.setPaintFlags(tvSdcard.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
tvSdcard.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 93);
}
});
swWatchdog.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@ -1565,6 +1586,8 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
}
});
grpVirusTotal.setVisibility(BuildConfig.PLAY_STORE_RELEASE ? View.GONE : View.VISIBLE);
grpUpdates.setVisibility(!BuildConfig.DEBUG &&
(Helper.isPlayStoreInstall() || !Helper.hasValidFingerprint(getContext()))
? View.GONE : View.VISIBLE);
@ -1780,6 +1803,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
swLanguageTool.setChecked(prefs.getBoolean("lt_enabled", false));
swDeepL.setChecked(prefs.getBoolean("deepl_enabled", false));
swVirusTotal.setChecked(prefs.getBoolean("vt_enabled", false));
swUpdates.setChecked(prefs.getBoolean("updates", true));
swCheckWeekly.setChecked(prefs.getBoolean("weekly", Helper.hasPlayStore(getContext())));
swCheckWeekly.setEnabled(swUpdates.isChecked());

@ -123,6 +123,7 @@ import com.google.android.material.snackbar.Snackbar;
import org.openintents.openpgp.util.OpenPgpApi;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@ -201,7 +202,6 @@ public class Helper {
static final String DONTKILL_URI = "https://dontkillmyapp.com/";
static final String URI_SUPPORT_RESET_OPEN = "https://support.google.com/pixelphone/answer/6271667";
static final String URI_SUPPORT_CONTACT_GROUP = "https://support.google.com/contacts/answer/30970";
static final String URI_VIRUS_TOTAL = "https://www.virustotal.com/";
// https://developer.android.com/distribute/marketing-tools/linking-to-google-play#PerformingSearch
private static final String PLAY_STORE_SEARCH = "https://play.google.com/store/search";
@ -2390,6 +2390,17 @@ public class Helper {
return hex(bytes);
}
static String getHash(InputStream is, String algorithm) throws NoSuchAlgorithmException, IOException {
MessageDigest digest = MessageDigest.getInstance(algorithm);
int count;
byte[] buffer = new byte[BUFFER_SIZE];
while ((count = is.read(buffer)) != -1)
digest.update(buffer, 0, count);
return hex(digest.digest());
}
static String hex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes)

@ -376,19 +376,30 @@
app:layout_constraintTop_toBottomOf="@id/swDeepL"
app:srcCompat="@drawable/twotone_info_24" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swVirusTotal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_advanced_virus_total"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ibDeepL"
app:switchPadding="12dp" />
<TextView
android:id="@+id/tvSdcard"
android:id="@+id/tvVirusTotalPrivacy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:drawableEnd="@drawable/twotone_open_in_new_12"
android:drawablePadding="6dp"
android:drawableTint="?android:attr/textColorLink"
android:text="@string/title_advanced_sdcard"
android:text="@string/title_privacy_policy"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="?android:attr/textColorLink"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ibDeepL" />
app:layout_constraintTop_toBottomOf="@id/swVirusTotal" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swUpdates"
@ -399,7 +410,7 @@
android:text="@string/title_advanced_updates"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvSdcard"
app:layout_constraintTop_toBottomOf="@id/tvVirusTotalPrivacy"
app:switchPadding="12dp" />
<ImageButton
@ -531,6 +542,26 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvCleanupHint" />
<TextView
android:id="@+id/tvSdcard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:drawableEnd="@drawable/twotone_open_in_new_12"
android:drawablePadding="6dp"
android:drawableTint="?android:attr/textColorLink"
android:text="@string/title_advanced_sdcard"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="?android:attr/textColorLink"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvLastCleanup" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpVirusTotal"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="swVirusTotal,tvVirusTotalPrivacy" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpUpdates"
android:layout_width="0dp"

@ -78,6 +78,7 @@
android:contentDescription="@string/title_legend_save"
android:padding="6dp"
android:scaleType="fitCenter"
android:tooltipText="@string/title_legend_save"
app:layout_constraintBottom_toBottomOf="@+id/tvType"
app:layout_constraintEnd_toStartOf="@+id/ibScan"
app:layout_constraintTop_toTopOf="parent"
@ -88,9 +89,10 @@
android:layout_width="36dp"
android:layout_height="36dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/title_legend_scan"
android:contentDescription="@string/title_check"
android:padding="6dp"
android:scaleType="fitCenter"
android:tooltipText="@string/title_check"
app:layout_constraintBottom_toBottomOf="@+id/tvType"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"

@ -733,6 +733,7 @@
<string name="title_advanced_language_system">System</string>
<string name="title_advanced_lt">LanguageTool integration</string>
<string name="title_advanced_deepl">DeepL integration</string>
<string name="title_advanced_virus_total">VirusTotal integration</string>
<string name="title_advanced_sdcard">I want to use an sdcard</string>
<string name="title_advanced_watchdog">Periodically check if FairEmail is still active</string>
<string name="title_advanced_updates">Check for GitHub updates</string>
@ -1840,7 +1841,6 @@
<string name="title_legend_sync_state">Synchronization state</string>
<string name="title_legend_download_state">Download state</string>
<string name="title_legend_save">Save</string>
<string name="title_legend_scan">Scan</string>
<string name="title_legend_delete">Delete</string>
<string name="title_legend_count">Count</string>
<string name="title_legend_folder_type">Folder type</string>

Loading…
Cancel
Save