Removed Gravatar/Libravatar from Play store release

This commit is contained in:
M66B 2022-06-09 09:19:15 +02:00
parent 8adf33ce9a
commit 86ed20a1bd
8 changed files with 209 additions and 118 deletions

View File

@ -37,9 +37,9 @@ FairEmail **can use** these services if they are explicitly enabled (off by defa
* [Thunderbird autoconfiguration](https://wiki.mozilla.org/Thunderbird:Autoconfiguration) – [Privacy policy](https://www.mozilla.org/privacy/)
* [DeepL](https://www.deepl.com/) – [Privacy policy](https://www.deepl.com/privacy/)
* [LanguageTool](https://languagetool.org/) – [Privacy policy](https://languagetool.org/legal/privacy)
* [Gravatar](https://gravatar.com/) – [Privacy policy](https://automattic.com/privacy/)
* [Libravatar](https://www.libravatar.org/) – [Privacy policy](https://www.libravatar.org/privacy/)
* [GitHub](https://github.com/) – [Privacy policy](https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement)
* [Gravatar](https://gravatar.com/) (GitHub version only) – [Privacy policy](https://automattic.com/privacy/)
* [Libravatar](https://www.libravatar.org/) (GitHub version only) – [Privacy policy](https://www.libravatar.org/privacy/)
* [GitHub](https://github.com/) (GitHub version only) – [Privacy policy](https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement)
FairEmail **can access** the websites at the domain names of email addresses (username@domain.name)
if [Brand Indicators for Message Identification](https://en.wikipedia.org/wiki/Brand_Indicators_for_Message_Identification) (BIMI)
@ -58,24 +58,24 @@ FairEmail **is** [GDPR compliant](https://gdpr.eu/).
This table provides a complete overview of all shared data and the conditions under which data will be shared:
| Service/function | Data sent | When the data will be sent |
| ----------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Mozilla autoconfig| Email address of email accounts | Upon configuring an email account |
| Email server | Login credentials, messages sent | Upon configuring and using an account or identity and upon sending messages |
| ipinfo.io | IP (network) address of domain names of links or email addresses | Upon pressing a button in the link confirmation dialog |
| Spamhaus | IP (network) address of domain names of links or email addresses | If spam blocklists are enabled, upon receiving a message |
| Spamcop | IP (network) address of domain names of links or email addresses | If spam blocklists are enabled, upon receiving a message |
| Barracuda | IP (network) address of domain names of links or email addresses | If spam blocklists are enabled, upon receiving a message |
| DeepL | Received or entered message text and target language code | Upon pressing a translate button |
| LanguageTool | Entered message texts | Upon long pressing the save draft button |
| Gravatar | [MD5 hash](https://en.wikipedia.org/wiki/MD5) of email addresses | If Gravatars are enabled, upon receiving a message |
| Libravatar | [MD5 hash](https://en.wikipedia.org/wiki/MD5) of email addresses | If Libravatars are enabled, upon receiving a message |
| GitHub | None, but see the remarks below | Upon downloading Disconnect's Tracker Protection lists |
| | | Upon checking for updates (GitHub version only) |
| BIMI | Domain name of email addresses | If BIMI is enabled, upon receiving a message |
| Favicons | Domain name of email addresses | If favicons are enabled, upon receiving a message |
| Link title | Link address | Upon pressing a button in the insert link dialog |
| Bugsnag | Information about warnings and errors | If error reporting is enabled, upon detecting an abnormal situation |
| Service/function | Data sent | When the data will be sent |
| ------------------ | ----------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Mozilla autoconfig | Email address of email accounts | Upon configuring an email account |
| Email server | Login credentials, messages sent | Upon configuring and using an account or identity and upon sending messages |
| ipinfo.io | IP (network) address of domain names of links or email addresses | Upon pressing a button in the link confirmation dialog |
| Spamhaus | IP (network) address of domain names of links or email addresses | If spam blocklists are enabled, upon receiving a message |
| Spamcop | IP (network) address of domain names of links or email addresses | If spam blocklists are enabled, upon receiving a message |
| Barracuda | IP (network) address of domain names of links or email addresses | If spam blocklists are enabled, upon receiving a message |
| DeepL | Received or entered message text and target language code | Upon pressing a translate button |
| LanguageTool | Entered message texts | Upon long pressing the save draft button |
| Gravatar | [MD5 hash](https://en.wikipedia.org/wiki/MD5) of email addresses | If Gravatars are enabled, upon receiving a message (GitHub version only) |
| Libravatar | [MD5 hash](https://en.wikipedia.org/wiki/MD5) of email addresses | If Libravatars are enabled, upon receiving a message (GitHub version only) |
| GitHub | None, but see the remarks below | Upon downloading Disconnect's Tracker Protection lists |
| | | Upon checking for updates (GitHub version only) |
| BIMI | Domain name of email addresses | If BIMI is enabled, upon receiving a message |
| Favicons | Domain name of email addresses | If favicons are enabled, upon receiving a message |
| Link title | Link address | Upon pressing a button in the insert link dialog |
| Bugsnag | Information about warnings and errors | If error reporting is enabled, upon detecting an abnormal situation |
All data is sent to improve the user experience in some way,
like to simplify account setup, identify spam and malicious messages, display message and sender information, find bugs and errors, etc.

View File

@ -0,0 +1,49 @@
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 org.apache.poi.ss.formula.eval.NotImplementedException;
import java.util.concurrent.Callable;
public class Avatar {
static final String GRAVATAR_PRIVACY_URI = "";
static final String LIBRAVATAR_PRIVACY_URI = "";
static Callable<ContactInfo.Favicon> getGravatar(String email, int scaleToPixels, Context context) {
return new Callable<ContactInfo.Favicon>() {
@Override
public ContactInfo.Favicon call() throws Exception {
throw new NotImplementedException("Gravatar");
}
};
}
static Callable<ContactInfo.Favicon> getLibravatar(String email, int scaleToPixels, Context context) {
return new Callable<ContactInfo.Favicon>() {
@Override
public ContactInfo.Favicon call() throws Exception {
throw new NotImplementedException("Libravatar");
}
};
}
}

View File

@ -0,0 +1,120 @@
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.graphics.Bitmap;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.Callable;
public class Avatar {
static final String GRAVATAR_PRIVACY_URI = "https://automattic.com/privacy/";
static final String LIBRAVATAR_PRIVACY_URI = "https://www.libravatar.org/privacy/";
private static final String GRAVATAR_URI = "https://www.gravatar.com/avatar/";
private static final int GRAVATAR_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
private static final int GRAVATAR_READ_TIMEOUT = 10 * 1000; // milliseconds
private static final int LIBRAVATAR_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
private static final int LIBRAVATAR_READ_TIMEOUT = 10 * 1000; // milliseconds
private static final String LIBRAVATAR_DNS = "_avatars-sec._tcp,_avatars._tcp";
private static final String LIBRAVATAR_URI = "https://seccdn.libravatar.org/avatar/";
static Callable<ContactInfo.Favicon> getGravatar(String email, int scaleToPixels, Context context) {
return new Callable<ContactInfo.Favicon>() {
@Override
public ContactInfo.Favicon call() throws Exception {
String hash = Helper.md5(email.getBytes());
URL url = new URL(GRAVATAR_URI + hash + "?d=404");
Log.i("Gravatar key=" + email + " url=" + url);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setReadTimeout(GRAVATAR_READ_TIMEOUT);
urlConnection.setConnectTimeout(GRAVATAR_CONNECT_TIMEOUT);
ConnectionHelper.setUserAgent(context, urlConnection);
urlConnection.connect();
try {
int status = urlConnection.getResponseCode();
if (status == HttpURLConnection.HTTP_OK) {
// Positive reply
Bitmap bitmap = ImageHelper.getScaledBitmap(urlConnection.getInputStream(), url.toString(), null, scaleToPixels);
return (bitmap == null ? null : new ContactInfo.Favicon(bitmap, "gravatar", false));
} else if (status == HttpURLConnection.HTTP_NOT_FOUND) {
// Negative reply
return null;
} else
throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage());
} finally {
urlConnection.disconnect();
}
}
};
}
static Callable<ContactInfo.Favicon> getLibravatar(String email, int scaleToPixels, Context context) {
return new Callable<ContactInfo.Favicon>() {
@Override
public ContactInfo.Favicon call() throws Exception {
String domain = UriHelper.getEmailDomain(email);
// https://wiki.libravatar.org/api/
String baseUrl = LIBRAVATAR_URI;
for (String dns : LIBRAVATAR_DNS.split(",")) {
DnsHelper.DnsRecord[] records = DnsHelper.lookup(context, dns + "." + domain, "srv");
if (records.length > 0) {
baseUrl = (records[0].port == 443 ? "https" : "http") + "://" + records[0].name + "/avatar/";
break;
}
}
String hash = Helper.md5(email.getBytes());
URL url = new URL(baseUrl + hash + "?d=404");
Log.i("Libravatar key=" + email + " url=" + url);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setReadTimeout(LIBRAVATAR_READ_TIMEOUT);
urlConnection.setConnectTimeout(LIBRAVATAR_CONNECT_TIMEOUT);
ConnectionHelper.setUserAgent(context, urlConnection);
urlConnection.connect();
try {
int status = urlConnection.getResponseCode();
if (status == HttpURLConnection.HTTP_OK) {
// Positive reply
Bitmap bitmap = ImageHelper.getScaledBitmap(urlConnection.getInputStream(), url.toString(), null, scaleToPixels);
return (bitmap == null ? null : new ContactInfo.Favicon(bitmap, "libravatar", false));
} else if (status == HttpURLConnection.HTTP_NOT_FOUND) {
// Negative reply
return null;
} else
throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage());
} finally {
urlConnection.disconnect();
}
}
};
}
}

View File

@ -6888,8 +6888,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
boolean contacts = Helper.hasPermission(context, Manifest.permission.READ_CONTACTS);
boolean avatars = prefs.getBoolean("avatars", true);
boolean bimi = prefs.getBoolean("bimi", false);
boolean gravatars = prefs.getBoolean("gravatars", false);
boolean libravatars = prefs.getBoolean("libravatars", false);
boolean gravatars = (prefs.getBoolean("gravatars", false) && !BuildConfig.PLAY_STORE_RELEASE);
boolean libravatars = (prefs.getBoolean("libravatars", false) && !BuildConfig.PLAY_STORE_RELEASE);
boolean favicons = prefs.getBoolean("favicons", false);
boolean generated = prefs.getBoolean("generated_icons", true);

View File

@ -117,14 +117,6 @@ public class ContactInfo {
private static final long CACHE_FAVICON_DURATION = 2 * 7 * 24 * 60 * 60 * 1000L; // milliseconds
private static final float MIN_FAVICON_LUMINANCE = 0.2f;
private static final String GRAVATAR_URI = "https://www.gravatar.com/avatar/";
private static final int GRAVATAR_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
private static final int GRAVATAR_READ_TIMEOUT = 10 * 1000; // milliseconds
private static final int LIBRAVATAR_CONNECT_TIMEOUT = 5 * 1000; // milliseconds
private static final int LIBRAVATAR_READ_TIMEOUT = 10 * 1000; // milliseconds
private static final String LIBRAVATAR_DNS = "_avatars-sec._tcp,_avatars._tcp";
private static final String LIBRAVATAR_URI = "https://seccdn.libravatar.org/avatar/";
// https://css-tricks.com/prefetching-preloading-prebrowsing/
// https://developer.mozilla.org/en-US/docs/Web/Performance/dns-prefetch
private static final List<String> REL_EXCLUDE = Collections.unmodifiableList(Arrays.asList(
@ -265,8 +257,8 @@ public class ContactInfo {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean avatars = prefs.getBoolean("avatars", true);
boolean bimi = prefs.getBoolean("bimi", false);
boolean gravatars = prefs.getBoolean("gravatars", false);
boolean libravatars = prefs.getBoolean("libravatars", false);
boolean gravatars = (prefs.getBoolean("gravatars", false) && !BuildConfig.PLAY_STORE_RELEASE);
boolean libravatars = (prefs.getBoolean("libravatars", false) && !BuildConfig.PLAY_STORE_RELEASE);
boolean favicons = prefs.getBoolean("favicons", false);
boolean generated = prefs.getBoolean("generated_icons", true);
boolean identicons = prefs.getBoolean("identicons", false);
@ -388,9 +380,9 @@ public class ContactInfo {
}));
if (gravatars)
futures.add(executorFavicon.submit(getGravatar(email, scaleToPixels, context)));
futures.add(executorFavicon.submit(Avatar.getGravatar(email, scaleToPixels, context)));
if (libravatars)
futures.add(executorFavicon.submit(getLibravatar(email, scaleToPixels, context)));
futures.add(executorFavicon.submit(Avatar.getLibravatar(email, scaleToPixels, context)));
if (favicons) {
String host = domain;
@ -876,85 +868,6 @@ public class ContactInfo {
}
}
static Callable<ContactInfo.Favicon> getGravatar(String email, int scaleToPixels, Context context) {
return new Callable<ContactInfo.Favicon>() {
@Override
public ContactInfo.Favicon call() throws Exception {
String hash = Helper.md5(email.getBytes());
URL url = new URL(GRAVATAR_URI + hash + "?d=404");
Log.i("Gravatar key=" + email + " url=" + url);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setReadTimeout(GRAVATAR_READ_TIMEOUT);
urlConnection.setConnectTimeout(GRAVATAR_CONNECT_TIMEOUT);
ConnectionHelper.setUserAgent(context, urlConnection);
urlConnection.connect();
try {
int status = urlConnection.getResponseCode();
if (status == HttpURLConnection.HTTP_OK) {
// Positive reply
Bitmap bitmap = ImageHelper.getScaledBitmap(urlConnection.getInputStream(), url.toString(), null, scaleToPixels);
return (bitmap == null ? null : new ContactInfo.Favicon(bitmap, "gravatar", false));
} else if (status == HttpURLConnection.HTTP_NOT_FOUND) {
// Negative reply
return null;
} else
throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage());
} finally {
urlConnection.disconnect();
}
}
};
}
static Callable<ContactInfo.Favicon> getLibravatar(String email, int scaleToPixels, Context context) {
return new Callable<ContactInfo.Favicon>() {
@Override
public ContactInfo.Favicon call() throws Exception {
String domain = UriHelper.getEmailDomain(email);
// https://wiki.libravatar.org/api/
String baseUrl = LIBRAVATAR_URI;
for (String dns : LIBRAVATAR_DNS.split(",")) {
DnsHelper.DnsRecord[] records = DnsHelper.lookup(context, dns + "." + domain, "srv");
if (records.length > 0) {
baseUrl = (records[0].port == 443 ? "https" : "http") + "://" + records[0].name + "/avatar/";
break;
}
}
String hash = Helper.md5(email.getBytes());
URL url = new URL(baseUrl + hash + "?d=404");
Log.i("Libravatar key=" + email + " url=" + url);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setReadTimeout(LIBRAVATAR_READ_TIMEOUT);
urlConnection.setConnectTimeout(LIBRAVATAR_CONNECT_TIMEOUT);
ConnectionHelper.setUserAgent(context, urlConnection);
urlConnection.connect();
try {
int status = urlConnection.getResponseCode();
if (status == HttpURLConnection.HTTP_OK) {
// Positive reply
Bitmap bitmap = ImageHelper.getScaledBitmap(urlConnection.getInputStream(), url.toString(), null, scaleToPixels);
return (bitmap == null ? null : new ContactInfo.Favicon(bitmap, "libravatar", false));
} else if (status == HttpURLConnection.HTTP_NOT_FOUND) {
// Negative reply
return null;
} else
throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage());
} finally {
urlConnection.disconnect();
}
}
};
}
private static boolean isRecoverable(Throwable ex, Context context) {
if (ex instanceof SocketTimeoutException) {
ConnectivityManager cm = Helper.getSystemService(context, ConnectivityManager.class);

View File

@ -180,6 +180,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
private SwitchCompat swAuthentication;
private SwitchCompat swAuthenticationIndicator;
private Group grpAvatar;
private Group grpUnzip;
private NumberFormat NF = NumberFormat.getNumberInstance();
@ -334,6 +335,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
swAuthentication = view.findViewById(R.id.swAuthentication);
swAuthenticationIndicator = view.findViewById(R.id.swAuthenticationIndicator);
grpAvatar = view.findViewById(R.id.grpAvatar);
grpUnzip = view.findViewById(R.id.grpUnzip);
List<StyleHelper.FontDescriptor> fonts = StyleHelper.getFonts(getContext());
@ -744,7 +746,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
tvGravatarPrivacy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.view(v.getContext(), Uri.parse(Helper.GRAVATAR_PRIVACY_URI), true);
Helper.view(v.getContext(), Uri.parse(Avatar.GRAVATAR_PRIVACY_URI), true);
}
});
@ -760,7 +762,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
tvLibravatarPrivacy.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.view(v.getContext(), Uri.parse(Helper.LIBRAVATAR_PRIVACY_URI), true);
Helper.view(v.getContext(), Uri.parse(Avatar.LIBRAVATAR_PRIVACY_URI), true);
}
});
@ -1257,6 +1259,7 @@ public class FragmentOptionsDisplay extends FragmentBase implements SharedPrefer
FragmentDialogTheme.setBackground(getContext(), view, false);
swFaviconsPartial.setText(getString(R.string.title_advanced_favicons_partial,
Helper.humanReadableByteCount(ContactInfo.FAVICON_READ_BYTES, false)));
grpAvatar.setVisibility(BuildConfig.PLAY_STORE_RELEASE ? View.GONE : View.VISIBLE);
grpUnzip.setVisibility(Build.VERSION.SDK_INT < Build.VERSION_CODES.O ? View.GONE : View.VISIBLE);
tvBimiUnverified.setVisibility(BuildConfig.DEBUG ? View.VISIBLE : View.GONE);

View File

@ -184,8 +184,6 @@ public class Helper {
static final String SUPPORT_URI = "https://contact.faircode.eu/";
static final String TEST_URI = "https://play.google.com/apps/testing/" + BuildConfig.APPLICATION_ID;
static final String BIMI_PRIVACY_URI = "https://datatracker.ietf.org/doc/html/draft-brotman-ietf-bimi-guidance-03#section-7.4";
static final String GRAVATAR_PRIVACY_URI = "https://automattic.com/privacy/";
static final String LIBRAVATAR_PRIVACY_URI = "https://www.libravatar.org/privacy/";
static final String ID_COMMAND_URI = "https://datatracker.ietf.org/doc/html/rfc2971#section-3.1";
static final String AUTH_RESULTS_URI = "https://datatracker.ietf.org/doc/html/rfc7601";
static final String FAVICON_PRIVACY_URI = "https://en.wikipedia.org/wiki/Favicon";

View File

@ -1524,6 +1524,14 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvPreviewLinesHint" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpAvatar"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="
swGravatars,tvGravatarsHint,tvGravatarPrivacy,
swLibravatars,tvLibravatarsHint,tvLibravatarPrivacy" />
</eu.faircode.email.ConstraintLayoutEx>
</androidx.cardview.widget.CardView>