Open images/favicons unsafe (http, invalid certificate, etc)

pull/209/head
M66B 2 years ago
parent 0f6cd802aa
commit ed93b3a432

@ -44,7 +44,10 @@ import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.InterfaceAddress; import java.net.InterfaceAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.URL;
import java.net.URLDecoder;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException; import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
@ -57,12 +60,17 @@ import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import inet.ipaddr.IPAddressString; import inet.ipaddr.IPAddressString;
public class ConnectionHelper { public class ConnectionHelper {
static final int MAX_REDIRECTS = 5; // https://www.freesoft.org/CIE/RFC/1945/46.htm
static final List<String> PREF_NETWORK = Collections.unmodifiableList(Arrays.asList( static final List<String> PREF_NETWORK = Collections.unmodifiableList(Arrays.asList(
"metered", "roaming", "rlah", "require_validated", "vpn_only" // update network state "metered", "roaming", "rlah", "require_validated", "vpn_only" // update network state
)); ));
@ -658,4 +666,72 @@ public class ConnectionHelper {
} }
} }
} }
static HttpURLConnection openConnectionUnsafe(Context context, String source, int ctimeout, int rtimeout) throws IOException {
return openConnectionUnsafe(context, new URL(source), ctimeout, rtimeout);
}
static HttpURLConnection openConnectionUnsafe(Context context, URL url, int ctimeout, int rtimeout) throws IOException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean open_unsafe = prefs.getBoolean("open_unsafe", true);
int redirects = 0;
while (true) {
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setDoOutput(false);
urlConnection.setReadTimeout(ctimeout);
urlConnection.setConnectTimeout(rtimeout);
urlConnection.setInstanceFollowRedirects(true);
if (urlConnection instanceof HttpsURLConnection) {
if (open_unsafe)
((HttpsURLConnection) urlConnection).setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
} else {
if (!open_unsafe)
throw new IOException("https required url=" + url);
}
ConnectionHelper.setUserAgent(context, urlConnection);
urlConnection.connect();
try {
int status = urlConnection.getResponseCode();
if (open_unsafe &&
(status == HttpURLConnection.HTTP_MOVED_PERM ||
status == HttpURLConnection.HTTP_MOVED_TEMP ||
status == HttpURLConnection.HTTP_SEE_OTHER ||
status == 307 /* Temporary redirect */ ||
status == 308 /* Permanent redirect */)) {
if (++redirects > MAX_REDIRECTS)
throw new IOException("Too many redirects");
String header = urlConnection.getHeaderField("Location");
if (header == null)
throw new IOException("Location header missing");
String location = URLDecoder.decode(header, StandardCharsets.UTF_8.name());
url = new URL(url, location);
Log.i("Redirect #" + redirects + " to " + url);
urlConnection.disconnect();
continue;
}
if (status != HttpURLConnection.HTTP_OK)
throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage());
return urlConnection;
} catch (IOException ex) {
urlConnection.disconnect();
throw ex;
}
}
}
} }

@ -80,13 +80,11 @@ import java.util.concurrent.Future;
import javax.mail.Address; import javax.mail.Address;
import javax.mail.internet.InternetAddress; import javax.mail.internet.InternetAddress;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLProtocolException; import javax.net.ssl.SSLProtocolException;
import javax.net.ssl.SSLSession;
public class ContactInfo { public class ContactInfo {
private String email; private String email;
@ -552,19 +550,8 @@ public class ContactInfo {
boolean favicons_partial = prefs.getBoolean("favicons_partial", true); boolean favicons_partial = prefs.getBoolean("favicons_partial", true);
Log.i("PARSE favicon " + base); Log.i("PARSE favicon " + base);
HttpsURLConnection connection = (HttpsURLConnection) base.openConnection(); HttpURLConnection connection = ConnectionHelper
connection.setRequestMethod("GET"); .openConnectionUnsafe(context, base, FAVICON_CONNECT_TIMEOUT, FAVICON_READ_TIMEOUT);
connection.setReadTimeout(FAVICON_READ_TIMEOUT);
connection.setConnectTimeout(FAVICON_CONNECT_TIMEOUT);
connection.setInstanceFollowRedirects(true);
connection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
ConnectionHelper.setUserAgent(context, connection);
connection.connect();
Document doc; Document doc;
try { try {
@ -840,19 +827,8 @@ public class ContactInfo {
if (!"https".equals(url.getProtocol())) if (!"https".equals(url.getProtocol()))
throw new FileNotFoundException(url.toString()); throw new FileNotFoundException(url.toString());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); HttpURLConnection connection = ConnectionHelper
connection.setRequestMethod("GET"); .openConnectionUnsafe(context, url, FAVICON_CONNECT_TIMEOUT, FAVICON_READ_TIMEOUT);
connection.setReadTimeout(FAVICON_READ_TIMEOUT);
connection.setConnectTimeout(FAVICON_CONNECT_TIMEOUT);
connection.setInstanceFollowRedirects(true);
connection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
ConnectionHelper.setUserAgent(context, connection);
connection.connect();
try { try {
int status = connection.getResponseCode(); int status = connection.getResponseCode();

@ -9568,7 +9568,7 @@ public class FragmentMessages extends FragmentBase
HttpURLConnection connection = null; HttpURLConnection connection = null;
try { try {
connection = Helper.openUrlRedirect(context, src, timeout); connection = ConnectionHelper.openConnectionUnsafe(context, src, timeout, timeout);
Helper.copy(connection.getInputStream(), os); Helper.copy(connection.getInputStream(), os);
} finally { } finally {
if (connection != null) if (connection != null)

@ -79,6 +79,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre
private SwitchCompat swSslHarden; private SwitchCompat swSslHarden;
private SwitchCompat swSslHardenStrict; private SwitchCompat swSslHardenStrict;
private SwitchCompat swCertStrict; private SwitchCompat swCertStrict;
private SwitchCompat swOpenUnsafe;
private Button btnManage; private Button btnManage;
private TextView tvNetworkMetered; private TextView tvNetworkMetered;
private TextView tvNetworkRoaming; private TextView tvNetworkRoaming;
@ -93,7 +94,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre
"download_headers", "download_eml", "download_plain", "download_headers", "download_eml", "download_plain",
"require_validated", "vpn_only", "require_validated", "vpn_only",
"timeout", "prefer_ip4", "bind_socket", "standalone_vpn", "tcp_keep_alive", "timeout", "prefer_ip4", "bind_socket", "standalone_vpn", "tcp_keep_alive",
"ssl_harden", "ssl_harden_strict", "cert_strict" "ssl_harden", "ssl_harden_strict", "cert_strict", "open_unsafe"
}; };
@Override @Override
@ -125,6 +126,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre
swSslHarden = view.findViewById(R.id.swSslHarden); swSslHarden = view.findViewById(R.id.swSslHarden);
swSslHardenStrict = view.findViewById(R.id.swSslHardenStrict); swSslHardenStrict = view.findViewById(R.id.swSslHardenStrict);
swCertStrict = view.findViewById(R.id.swCertStrict); swCertStrict = view.findViewById(R.id.swCertStrict);
swOpenUnsafe = view.findViewById(R.id.swOpenUnsafe);
btnManage = view.findViewById(R.id.btnManage); btnManage = view.findViewById(R.id.btnManage);
tvNetworkMetered = view.findViewById(R.id.tvNetworkMetered); tvNetworkMetered = view.findViewById(R.id.tvNetworkMetered);
@ -307,6 +309,13 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre
} }
}); });
swOpenUnsafe.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("open_unsafe", checked).apply();
}
});
final Intent manage = getIntentConnectivity(); final Intent manage = getIntentConnectivity();
PackageManager pm = getContext().getPackageManager(); PackageManager pm = getContext().getPackageManager();
btnManage.setVisibility( btnManage.setVisibility(
@ -441,6 +450,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre
swSslHardenStrict.setChecked(prefs.getBoolean("ssl_harden_strict", false)); swSslHardenStrict.setChecked(prefs.getBoolean("ssl_harden_strict", false));
swSslHardenStrict.setEnabled(swSslHarden.isChecked()); swSslHardenStrict.setEnabled(swSslHarden.isChecked());
swCertStrict.setChecked(prefs.getBoolean("cert_strict", !BuildConfig.PLAY_STORE_RELEASE)); swCertStrict.setChecked(prefs.getBoolean("cert_strict", !BuildConfig.PLAY_STORE_RELEASE));
swOpenUnsafe.setChecked(prefs.getBoolean("open_unsafe", true));
} }
private static Intent getIntentConnectivity() { private static Intent getIntentConnectivity() {

@ -135,9 +135,6 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
@ -178,7 +175,6 @@ public class Helper {
static final int BUFFER_SIZE = 8192; // Same as in Files class static final int BUFFER_SIZE = 8192; // Same as in Files class
static final long MIN_REQUIRED_SPACE = 250 * 1024L * 1024L; static final long MIN_REQUIRED_SPACE = 250 * 1024L * 1024L;
static final int MAX_REDIRECTS = 5; // https://www.freesoft.org/CIE/RFC/1945/46.htm
static final int AUTOLOCK_GRACE = 15; // seconds static final int AUTOLOCK_GRACE = 15; // seconds
static final long PIN_FAILURE_DELAY = 3; // seconds static final long PIN_FAILURE_DELAY = 3; // seconds
@ -2359,53 +2355,6 @@ public class Helper {
//intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.fromFile(initial)); //intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.fromFile(initial));
} }
static HttpURLConnection openUrlRedirect(Context context, String source, int timeout) throws IOException {
int redirects = 0;
URL url = new URL(source);
while (true) {
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setDoOutput(false);
urlConnection.setReadTimeout(timeout);
urlConnection.setConnectTimeout(timeout);
urlConnection.setInstanceFollowRedirects(true);
ConnectionHelper.setUserAgent(context, urlConnection);
urlConnection.connect();
try {
int status = urlConnection.getResponseCode();
if (status == HttpURLConnection.HTTP_MOVED_PERM ||
status == HttpURLConnection.HTTP_MOVED_TEMP ||
status == HttpURLConnection.HTTP_SEE_OTHER ||
status == 307 /* Temporary redirect */ ||
status == 308 /* Permanent redirect */) {
if (++redirects > MAX_REDIRECTS)
throw new IOException("Too many redirects");
String header = urlConnection.getHeaderField("Location");
if (header == null)
throw new IOException("Location header missing");
String location = URLDecoder.decode(header, StandardCharsets.UTF_8.name());
url = new URL(url, location);
Log.i("Redirect #" + redirects + " to " + url);
urlConnection.disconnect();
continue;
}
if (status != HttpURLConnection.HTTP_OK)
throw new IOException("Error " + status + ": " + urlConnection.getResponseMessage());
return urlConnection;
} catch (IOException ex) {
urlConnection.disconnect();
throw ex;
}
}
}
static class ByteArrayInOutStream extends ByteArrayOutputStream { static class ByteArrayInOutStream extends ByteArrayOutputStream {
public ByteArrayInOutStream() { public ByteArrayInOutStream() {
super(); super();

@ -692,7 +692,7 @@ class ImageHelper {
Bitmap bm; Bitmap bm;
HttpURLConnection urlConnection = null; HttpURLConnection urlConnection = null;
try { try {
urlConnection = Helper.openUrlRedirect(context, source, timeout); urlConnection = ConnectionHelper.openConnectionUnsafe(context, source, timeout, timeout);
if (id > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (id > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
File file = getCacheFile(context, id, source, ".blob"); File file = getCacheFile(context, id, source, ".blob");

@ -209,12 +209,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawableStart="@drawable/twotone_warning_24" android:drawableStart="@drawable/twotone_warning_24"
android:drawablePadding="6dp" android:drawablePadding="6dp"
app:drawableTint="?attr/colorWarning"
android:gravity="center" android:gravity="center"
android:text="@string/title_advanced_advanced" android:text="@string/title_advanced_advanced"
android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textColor="?attr/colorWarning" android:textColor="?attr/colorWarning"
android:textStyle="bold" android:textStyle="bold"
app:drawableTint="?attr/colorWarning"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -483,6 +483,30 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swCertStrict" /> app:layout_constraintTop_toBottomOf="@id/swCertStrict" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swOpenUnsafe"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:checked="true"
android:text="@string/title_advanced_open_unsafe"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvCertStrictHint"
app:switchPadding="12dp" />
<TextView
android:id="@+id/tvOpenUnsafeHint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="48dp"
android:text="@string/title_advanced_open_unsafe_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/swOpenUnsafe" />
<Button <Button
android:id="@+id/btnManage" android:id="@+id/btnManage"
style="?android:attr/buttonStyleSmall" style="?android:attr/buttonStyleSmall"
@ -493,7 +517,7 @@
android:drawablePadding="6dp" android:drawablePadding="6dp"
android:text="@string/title_advanced_manage_connectivity" android:text="@string/title_advanced_manage_connectivity"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvCertStrictHint" /> app:layout_constraintTop_toBottomOf="@id/tvOpenUnsafeHint" />
<TextView <TextView
android:id="@+id/tvNetworkMetered" android:id="@+id/tvNetworkMetered"
@ -556,11 +580,11 @@
android:drawableStart="@drawable/twotone_warning_24" android:drawableStart="@drawable/twotone_warning_24"
android:drawableEnd="@drawable/twotone_warning_24" android:drawableEnd="@drawable/twotone_warning_24"
android:drawablePadding="6dp" android:drawablePadding="6dp"
app:drawableTint="?attr/colorWarning"
android:gravity="center" android:gravity="center"
android:text="@string/title_advanced_caption_debug" android:text="@string/title_advanced_caption_debug"
android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textStyle="bold" android:textStyle="bold"
app:drawableTint="?attr/colorWarning"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />

@ -473,6 +473,7 @@
<string name="title_advanced_ssl_harden">Harden SSL connections</string> <string name="title_advanced_ssl_harden">Harden SSL connections</string>
<string name="title_advanced_ssl_harden_strict">Require TLS 1.3</string> <string name="title_advanced_ssl_harden_strict">Require TLS 1.3</string>
<string name="title_advanced_cert_strict">Strict certificate checking</string> <string name="title_advanced_cert_strict">Strict certificate checking</string>
<string name="title_advanced_open_unsafe">Open insecure connections</string>
<string name="title_advanced_manage_connectivity">Manage connectivity</string> <string name="title_advanced_manage_connectivity">Manage connectivity</string>
<string name="title_advanced_caption_general">General</string> <string name="title_advanced_caption_general">General</string>
@ -865,6 +866,7 @@
<string name="title_advanced_timeout_hint">The read/write timeout will be set to the double of the connection timeout. Higher values will result in more battery use.</string> <string name="title_advanced_timeout_hint">The read/write timeout will be set to the double of the connection timeout. Higher values will result in more battery use.</string>
<string name="title_advanced_cert_strict_hint">Disabling this will relax checking of server certificates</string> <string name="title_advanced_cert_strict_hint">Disabling this will relax checking of server certificates</string>
<string name="title_advanced_ssl_harden_hint">Enabling this will disable weak SSL protocols and ciphers, which can lead to connection problems</string> <string name="title_advanced_ssl_harden_hint">Enabling this will disable weak SSL protocols and ciphers, which can lead to connection problems</string>
<string name="title_advanced_open_unsafe_hint">Disabling this will require secure connections to download images</string>
<string name="title_advanced_roaming_hint">Messages headers will always be fetched when roaming. You can use the device\'s roaming setting to disable internet while roaming.</string> <string name="title_advanced_roaming_hint">Messages headers will always be fetched when roaming. You can use the device\'s roaming setting to disable internet while roaming.</string>
<string name="title_advanced_browse_hint">Fetch more messages when scrolling down</string> <string name="title_advanced_browse_hint">Fetch more messages when scrolling down</string>

Loading…
Cancel
Save