diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 96d790ccba..f86a782bad 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -25,3 +25,4 @@ FairEmail uses: * [Java™ Architecture for XML Binding](https://github.com/eclipse-ee4j/jaxb-ri). Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. [GNU General Public License Version 2](https://github.com/eclipse-ee4j/jaxb-ri/blob/master/jaxb-ri/LICENSE.md). * [File Icon Images](https://github.com/dmhendricks/file-icon-vectors). A collection of file type/extension SVG icons, available free for use in your applications. [MIT License](https://github.com/dmhendricks/file-icon-vectors/blob/master/LICENSE). * [GPX file type icon](https://www.flaticon.com/free-icon/gpx-file-format-variant_29258) made by [Freepik](https://www.flaticon.com/authors/freepik) from [Flaticon](https://www.flaticon.com). +* [Disconnect's tracker protection lists](https://github.com/disconnectme/disconnect-tracking-protection). Copyright 2010-2020 Disconnect, Inc. [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International license](https://github.com/disconnectme/disconnect-tracking-protection/blob/master/LICENSE). diff --git a/app/src/main/assets/ATTRIBUTION.md b/app/src/main/assets/ATTRIBUTION.md index 96d790ccba..f86a782bad 100644 --- a/app/src/main/assets/ATTRIBUTION.md +++ b/app/src/main/assets/ATTRIBUTION.md @@ -25,3 +25,4 @@ FairEmail uses: * [Java™ Architecture for XML Binding](https://github.com/eclipse-ee4j/jaxb-ri). Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved. [GNU General Public License Version 2](https://github.com/eclipse-ee4j/jaxb-ri/blob/master/jaxb-ri/LICENSE.md). * [File Icon Images](https://github.com/dmhendricks/file-icon-vectors). A collection of file type/extension SVG icons, available free for use in your applications. [MIT License](https://github.com/dmhendricks/file-icon-vectors/blob/master/LICENSE). * [GPX file type icon](https://www.flaticon.com/free-icon/gpx-file-format-variant_29258) made by [Freepik](https://www.flaticon.com/authors/freepik) from [Flaticon](https://www.flaticon.com). +* [Disconnect's tracker protection lists](https://github.com/disconnectme/disconnect-tracking-protection). Copyright 2010-2020 Disconnect, Inc. [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International license](https://github.com/disconnectme/disconnect-tracking-protection/blob/master/LICENSE). diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index b957971318..c5c41518de 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -5772,6 +5772,7 @@ public class AdapterMessage extends RecyclerView.Adapter>() { + @Override + protected void onPreExecute(Bundle args) { + tvDisconnect.setVisibility(View.GONE); + } + + @Override + protected List onExecute(Context context, Bundle args) throws Throwable { + Uri uri = args.getParcelable("uri"); + return DisconnectBlacklist.getCategories(uri.getHost(), context); + } + + @Override + protected void onExecuted(Bundle args, List data) { + tvDisconnect.setText(data == null ? null : TextUtils.join(", ", data)); + tvDisconnect.setVisibility(data == null ? View.GONE : View.VISIBLE); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Log.unexpectedError(getParentFragmentManager(), ex); + } + }.execute(getContext(), getViewLifecycleOwner(), args, "disconnect"); + final Context context = getContext(); return new AlertDialog.Builder(context) diff --git a/app/src/main/java/eu/faircode/email/DisconnectBlacklist.java b/app/src/main/java/eu/faircode/email/DisconnectBlacklist.java new file mode 100644 index 0000000000..4bc69dc520 --- /dev/null +++ b/app/src/main/java/eu/faircode/email/DisconnectBlacklist.java @@ -0,0 +1,117 @@ +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-2020 by Marcel Bokhorst (M66B) +*/ + +import android.content.Context; +import android.net.Uri; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.net.ssl.HttpsURLConnection; + +public class DisconnectBlacklist { + private final static int FETCH_TIMEOUT = 20 * 1000; // milliseconds + private final static String LIST = "https://raw.githubusercontent.com/mozilla-services/shavar-prod-lists/master/disconnect-blacklist.json"; + + static void download(Context context) throws IOException, JSONException { + File file = getFile(context); + + URL url = new URL(LIST); + Log.i("GET " + url); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setReadTimeout(FETCH_TIMEOUT); + connection.setConnectTimeout(FETCH_TIMEOUT); + connection.connect(); + + try { + String response = Helper.readStream(connection.getInputStream(), StandardCharsets.UTF_8.name()); + Helper.writeText(file, response); + } finally { + connection.disconnect(); + } + } + + static List getCategories(String domain, Context context) throws IOException, JSONException { + if (domain == null) + return null; + + File file = getFile(context); + if (!file.exists()) + return null; + + List result = new ArrayList<>(); + + String json = Helper.readText(file); + JSONObject jdisconnect = new JSONObject(json); + JSONObject jcategories = (JSONObject) jdisconnect.get("categories"); + Iterator categories = jcategories.keys(); + while (categories.hasNext()) { + String category = categories.next(); + JSONArray jcategory = jcategories.getJSONArray(category); + for (int c = 0; c < jcategory.length(); c++) { + JSONObject jblock = (JSONObject) jcategory.get(c); + Iterator names = jblock.keys(); + if (names.hasNext()) { + String name = names.next(); + JSONObject jsites = (JSONObject) jblock.get(name); + Iterator sites = jsites.keys(); + if (sites.hasNext()) { + List domains = new ArrayList<>(); + + String site = sites.next(); + String host = Uri.parse(site).getHost(); + if (host != null) + domains.add(host); + + JSONArray jdomains = jsites.getJSONArray(site); + for (int d = 0; d < jdomains.length(); d++) + domains.add(jdomains.getString(d)); + + for (String d : domains) + if (domain.equalsIgnoreCase(d) && !result.contains(category)) + result.add(category); + } + } + } + } + + return (result.size() == 0 ? null : result); + } + + private static File getFile(Context context) { + return new File(context.getFilesDir(), "disconnect-blacklist.json"); + } + + static Long getTime(Context context) { + File file = getFile(context); + return (file.exists() ? file.lastModified() : null); + } +} diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsPrivacy.java b/app/src/main/java/eu/faircode/email/FragmentOptionsPrivacy.java index 45deaa6dca..6632cb6dff 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsPrivacy.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsPrivacy.java @@ -20,10 +20,10 @@ package eu.faircode.email; */ import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -55,6 +55,9 @@ import androidx.constraintlayout.widget.Group; import androidx.lifecycle.Lifecycle; import androidx.preference.PreferenceManager; +import java.text.DateFormat; +import java.text.SimpleDateFormat; + public class FragmentOptionsPrivacy extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener { private SwitchCompat swConfirmLinks; private SwitchCompat swBrowseLinks; @@ -69,6 +72,9 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer private SwitchCompat swSecure; private SwitchCompat swSafeBrowsing; private ImageButton ibSafeBrowsing; + private ImageButton ibDisconnectBlacklist; + private Button btnDisconnectBlacklist; + private TextView tvDisconnectBlacklistTime; private Group grpSafeBrowsing; @@ -85,7 +91,6 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer setSubtitle(R.string.title_setup); setHasOptionsMenu(true); - PackageManager pm = getContext().getPackageManager(); View view = inflater.inflate(R.layout.fragment_options_privacy, container, false); // Get controls @@ -103,6 +108,9 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer swSecure = view.findViewById(R.id.swSecure); swSafeBrowsing = view.findViewById(R.id.swSafeBrowsing); ibSafeBrowsing = view.findViewById(R.id.ibSafeBrowsing); + ibDisconnectBlacklist = view.findViewById(R.id.ibDisconnectBlacklist); + btnDisconnectBlacklist = view.findViewById(R.id.btnDisconnectBlacklist); + tvDisconnectBlacklistTime = view.findViewById(R.id.tvDisconnectBlacklistTime); grpSafeBrowsing = view.findViewById(R.id.grpSafeBrowsing); @@ -237,6 +245,46 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer grpSafeBrowsing.setVisibility(Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? View.GONE : View.VISIBLE); + ibDisconnectBlacklist.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Helper.view(getContext(), Uri.parse("https://github.com/disconnectme/disconnect-tracking-protection"), true); + } + }); + + btnDisconnectBlacklist.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + new SimpleTask() { + @Override + protected void onPreExecute(Bundle args) { + btnDisconnectBlacklist.setEnabled(false); + } + + @Override + protected void onPostExecute(Bundle args) { + btnDisconnectBlacklist.setEnabled(true); + } + + @Override + protected Void onExecute(Context context, Bundle args) throws Throwable { + DisconnectBlacklist.download(context); + return null; + } + + @Override + protected void onExecuted(Bundle args, Void data) { + setOptions(); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Log.unexpectedError(getParentFragmentManager(), ex); + } + }.execute(FragmentOptionsPrivacy.this, new Bundle(), "disconnect"); + } + }); + PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this); return view; @@ -308,6 +356,10 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer swDisplayHidden.setChecked(prefs.getBoolean("display_hidden", false)); swSecure.setChecked(prefs.getBoolean("secure", false)); swSafeBrowsing.setChecked(prefs.getBoolean("safe_browsing", false)); + + Long time = DisconnectBlacklist.getTime(getContext()); + DateFormat DF = SimpleDateFormat.getDateTimeInstance(); + tvDisconnectBlacklistTime.setText(time == null ? null : DF.format(time)); } public static class FragmentDialogPin extends FragmentDialogBase { diff --git a/app/src/main/res/layout/dialog_open_link.xml b/app/src/main/res/layout/dialog_open_link.xml index f2cbfdff44..e46c550075 100644 --- a/app/src/main/res/layout/dialog_open_link.xml +++ b/app/src/main/res/layout/dialog_open_link.xml @@ -88,6 +88,18 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/ibCopy" /> + + + app:layout_constraintTop_toBottomOf="@id/tvDisconnect" /> + + + + +