diff --git a/app/src/dummy/java/eu/faircode/email/Check.java b/app/src/dummy/java/eu/faircode/email/VirusTotal.java
similarity index 79%
rename from app/src/dummy/java/eu/faircode/email/Check.java
rename to app/src/dummy/java/eu/faircode/email/VirusTotal.java
index c7618f95c9..5cee1d5483 100644
--- a/app/src/dummy/java/eu/faircode/email/Check.java
+++ b/app/src/dummy/java/eu/faircode/email/VirusTotal.java
@@ -20,14 +20,14 @@ package eu.faircode.email;
*/
import android.content.Context;
+import android.os.Bundle;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.LifecycleOwner;
+import java.io.File;
-public class Check {
+public class VirusTotal {
static final String URI_PRIVACY = "";
- static void virus(Context context, LifecycleOwner owner, FragmentManager fm, long id) {
- // Do nothing
+ static Bundle scan(Context context, File file) {
+ return null;
}
}
diff --git a/app/src/extra/java/eu/faircode/email/Check.java b/app/src/extra/java/eu/faircode/email/Check.java
deleted file mode 100644
index bcb2685276..0000000000
--- a/app/src/extra/java/eu/faircode/email/Check.java
+++ /dev/null
@@ -1,150 +0,0 @@
-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 .
-
- Copyright 2018-2022 by Marcel Bokhorst (M66B)
-*/
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceManager;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.net.URL;
-
-import javax.net.ssl.HttpsURLConnection;
-
-public class Check {
- static final String URI_VT_PRIVACY = "https://support.virustotal.com/hc/en-us/articles/115002168385-Privacy-Policy";
- private static final int VT_TIMEOUT = 20; // seconds
- private static final String URI_VT_ENDPOINT = "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() {
- @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;
-
- String hash;
- try (InputStream is = new FileInputStream(attachment.getFile(context))) {
- hash = Helper.getHash(is, "SHA-256");
- }
-
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- String apikey = prefs.getString("vt_apikey", null);
-
- if (!TextUtils.isEmpty(apikey)) {
- URL url = new URL(URI_VT_ENDPOINT + "api/v3/files/" + hash);
- HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
- connection.setRequestMethod("GET");
- connection.setReadTimeout(VT_TIMEOUT * 1000);
- connection.setConnectTimeout(VT_TIMEOUT * 1000);
- ConnectionHelper.setUserAgent(context, connection);
- connection.setRequestProperty("x-apikey", apikey);
- connection.connect();
-
- try {
- int status = connection.getResponseCode();
- if (status != HttpsURLConnection.HTTP_OK && status != HttpsURLConnection.HTTP_NOT_FOUND) {
- String error = "Error " + status + ": " + connection.getResponseMessage();
- try {
- InputStream is = connection.getErrorStream();
- if (is != null)
- error += "\n" + Helper.readStream(is);
- } catch (Throwable ex) {
- Log.w(ex);
- }
- throw new FileNotFoundException(error);
- }
-
- if (status == HttpsURLConnection.HTTP_OK) {
- String response = Helper.readStream(connection.getInputStream());
- Log.i("VT response=" + response);
-
- // https://developers.virustotal.com/reference/files
- // Example: https://gist.github.com/M66B/4ea95fdb93fb10bf4047761fcc9ec21a
- JSONObject jroot = new JSONObject(response);
- JSONObject jdata = jroot.getJSONObject("data");
- JSONObject jattributes = jdata.getJSONObject("attributes");
-
- JSONObject jclassification = jattributes.getJSONObject("popular_threat_classification");
- String label = jclassification.getString("suggested_threat_label");
-
- int count = 0;
- int malicious = 0;
- JSONObject jlast_analysis_results = jattributes.getJSONObject("last_analysis_results");
- JSONArray jnames = jlast_analysis_results.names();
- for (int i = 0; i < jnames.length(); i++) {
- String name = jnames.getString(i);
- JSONObject jresult = jlast_analysis_results.getJSONObject(name);
- String category = jresult.getString("category");
- Log.i("VT " + name + "=" + category);
- if (!"type-unsupported".equals(category))
- count++;
- if ("malicious".equals(category))
- malicious++;
- }
-
- Log.i("VT label=" + label + " " + malicious + "/" + count);
-
- args.putString("label", label);
- args.putInt("count", count);
- args.putInt("malicious", malicious);
- }
- } finally {
- connection.disconnect();
- }
- }
-
- return hash;
- }
-
- @Override
- protected void onExecuted(Bundle args, String hash) {
- if (hash == null)
- return;
-
- Uri uri = Uri.parse(URI_VT_ENDPOINT + "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");
- }
-}
diff --git a/app/src/extra/java/eu/faircode/email/VirusTotal.java b/app/src/extra/java/eu/faircode/email/VirusTotal.java
new file mode 100644
index 0000000000..e006aba8c7
--- /dev/null
+++ b/app/src/extra/java/eu/faircode/email/VirusTotal.java
@@ -0,0 +1,126 @@
+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 .
+
+ Copyright 2018-2022 by Marcel Bokhorst (M66B)
+*/
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import androidx.preference.PreferenceManager;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.HttpsURLConnection;
+
+public class VirusTotal {
+ static final String URI_ENDPOINT = "https://www.virustotal.com/";
+ static final String URI_PRIVACY = "https://support.virustotal.com/hc/en-us/articles/115002168385-Privacy-Policy";
+
+ private static final int VT_TIMEOUT = 20; // seconds
+
+ static Bundle scan(Context context, File file) throws NoSuchAlgorithmException, IOException, JSONException {
+ String hash;
+ try (InputStream is = new FileInputStream(file)) {
+ hash = Helper.getHash(is, "SHA-256");
+ }
+
+ Bundle result = new Bundle();
+ result.putString("uri", URI_ENDPOINT + "gui/file/" + hash);
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String apikey = prefs.getString("vt_apikey", null);
+ if (TextUtils.isEmpty(apikey))
+ return result;
+
+ URL url = new URL(URI_ENDPOINT + "api/v3/files/" + hash);
+ HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
+ connection.setRequestMethod("GET");
+ connection.setReadTimeout(VT_TIMEOUT * 1000);
+ connection.setConnectTimeout(VT_TIMEOUT * 1000);
+ ConnectionHelper.setUserAgent(context, connection);
+ connection.setRequestProperty("x-apikey", apikey);
+ connection.connect();
+
+ try {
+ int status = connection.getResponseCode();
+ if (status != HttpsURLConnection.HTTP_OK && status != HttpsURLConnection.HTTP_NOT_FOUND) {
+ String error = "Error " + status + ": " + connection.getResponseMessage();
+ try {
+ InputStream is = connection.getErrorStream();
+ if (is != null)
+ error += "\n" + Helper.readStream(is);
+ } catch (Throwable ex) {
+ Log.w(ex);
+ }
+ throw new FileNotFoundException(error);
+ }
+
+ if (status == HttpsURLConnection.HTTP_OK) {
+ String response = Helper.readStream(connection.getInputStream());
+ Log.i("VT response=" + response);
+
+ // https://developers.virustotal.com/reference/files
+ // Example: https://gist.github.com/M66B/4ea95fdb93fb10bf4047761fcc9ec21a
+ JSONObject jroot = new JSONObject(response);
+ JSONObject jdata = jroot.getJSONObject("data");
+ JSONObject jattributes = jdata.getJSONObject("attributes");
+
+ JSONObject jclassification = jattributes.getJSONObject("popular_threat_classification");
+ String label = jclassification.getString("suggested_threat_label");
+
+ int count = 0;
+ int malicious = 0;
+ JSONObject janalysis = jattributes.getJSONObject("last_analysis_results");
+ JSONArray jnames = janalysis.names();
+ for (int i = 0; i < jnames.length(); i++) {
+ String name = jnames.getString(i);
+ JSONObject jresult = janalysis.getJSONObject(name);
+ String category = jresult.getString("category");
+ Log.i("VT " + name + "=" + category);
+ if (!"type-unsupported".equals(category))
+ count++;
+ if ("malicious".equals(category))
+ malicious++;
+ }
+
+ Log.i("VT label=" + label + " " + malicious + "/" + count);
+
+ result.putString("label", label);
+ result.putInt("count", count);
+ result.putInt("malicious", malicious);
+ }
+ } finally {
+ connection.disconnect();
+ }
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/eu/faircode/email/AdapterAttachment.java b/app/src/main/java/eu/faircode/email/AdapterAttachment.java
index 0931b26133..1163744f56 100644
--- a/app/src/main/java/eu/faircode/email/AdapterAttachment.java
+++ b/app/src/main/java/eu/faircode/email/AdapterAttachment.java
@@ -215,7 +215,7 @@ public class AdapterAttachment extends RecyclerView.Adapter() {
+ @Override
+ protected Bundle 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;
+
+ return VirusTotal.scan(context, attachment.getFile(context));
+ }
+
+ @Override
+ protected void onExecuted(Bundle args, Bundle result) {
+ String uri = result.getString("uri");
+ Helper.view(context, Uri.parse(uri), true);
+ }
+
+ @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);
diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java
index d5e65799c6..68fad2278f 100644
--- a/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java
+++ b/app/src/main/java/eu/faircode/email/FragmentOptionsMisc.java
@@ -655,7 +655,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
tvVirusTotalPrivacy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- Helper.view(v.getContext(), Uri.parse(Check.URI_VT_PRIVACY), true);
+ Helper.view(v.getContext(), Uri.parse(VirusTotal.URI_PRIVACY), true);
}
});