X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Fdialogs%2FSslCertificateErrorDialog.java;h=7d8e0b6384b103b95976d940bc72f5695037a5e6;hp=8e140d28c3766fc276f8e22ebe7c4a2595ad960f;hb=fa3b8b382eb5ed86c598e3b126d1ef5dd117c5be;hpb=91d9e1ea664c90aa799468d48255ff40a2531f10 diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java index 8e140d28..7d8e0b63 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Soren Stoutner . + * Copyright © 2016-2019 Soren Stoutner . * * This file is part of Privacy Browser . * @@ -20,41 +20,42 @@ package com.stoutner.privacybrowser.dialogs; import android.annotation.SuppressLint; +import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; -import android.content.Context; import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.net.Uri; import android.net.http.SslCertificate; import android.net.http.SslError; +import android.os.AsyncTask; import android.os.Bundle; -import android.support.annotation.NonNull; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. -import android.support.v7.app.AppCompatDialogFragment; +import android.preference.PreferenceManager; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.webkit.SslErrorHandler; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.fragment.app.DialogFragment; // The AndroidX dialog fragment must be used or an error is produced on API <=22. + import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.activities.MainWebViewActivity; +import com.stoutner.privacybrowser.fragments.WebViewTabFragment; +import com.stoutner.privacybrowser.views.NestedScrollWebView; +import java.lang.ref.WeakReference; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.text.DateFormat; import java.util.Date; -public class SslCertificateErrorDialog extends AppCompatDialogFragment { - - private int primaryErrorInt; - private String urlWithError; - private String issuedToCName; - private String issuedToOName; - private String issuedToUName; - private String issuedByCName; - private String issuedByOName; - private String issuedByUName; - private String startDate; - private String endDate; - - public static SslCertificateErrorDialog displayDialog(SslError error) { +public class SslCertificateErrorDialog extends DialogFragment { + public static SslCertificateErrorDialog displayDialog(SslError error, long webViewFragmentId) { // Get the various components of the SSL error message. int primaryErrorIntForBundle = error.getPrimaryError(); String urlWithErrorForBundle = error.getUrl(); @@ -68,114 +69,170 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment { Date startDateForBundle = sslCertificate.getValidNotBeforeDate(); Date endDateForBundle = sslCertificate.getValidNotAfterDate(); - // Store the SSL error message components in a `Bundle`. + // Create an arguments bundle. Bundle argumentsBundle = new Bundle(); - argumentsBundle.putInt("PrimaryErrorInt", primaryErrorIntForBundle); - argumentsBundle.putString("UrlWithError", urlWithErrorForBundle); - argumentsBundle.putString("IssuedToCName", issuedToCNameForBundle); - argumentsBundle.putString("IssuedToOName", issuedToONameForBundle); - argumentsBundle.putString("IssuedToUName", issuedToUNameForBundle); - argumentsBundle.putString("IssuedByCName", issuedByCNameForBundle); - argumentsBundle.putString("IssuedByOName", issuedByONameForBundle); - argumentsBundle.putString("IssuedByUName", issuedByUNameForBundle); - argumentsBundle.putString("StartDate", DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDateForBundle)); - argumentsBundle.putString("EndDate", DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDateForBundle)); - - // Add `argumentsBundle` to this instance of `SslCertificateErrorDialog`. + + // Store the SSL error message components in a `Bundle`. + argumentsBundle.putInt("primary_error_int", primaryErrorIntForBundle); + argumentsBundle.putString("url_with_error", urlWithErrorForBundle); + argumentsBundle.putString("issued_to_cname", issuedToCNameForBundle); + argumentsBundle.putString("issued_to_oname", issuedToONameForBundle); + argumentsBundle.putString("issued_to_uname", issuedToUNameForBundle); + argumentsBundle.putString("issued_by_cname", issuedByCNameForBundle); + argumentsBundle.putString("issued_by_oname", issuedByONameForBundle); + argumentsBundle.putString("issued_by_uname", issuedByUNameForBundle); + argumentsBundle.putString("start_date", DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDateForBundle)); + argumentsBundle.putString("end_date", DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDateForBundle)); + argumentsBundle.putLong("webview_fragment_id", webViewFragmentId); + + // Create a new instance of the SSL certificate error dialog. SslCertificateErrorDialog thisSslCertificateErrorDialog = new SslCertificateErrorDialog(); + + // Add the arguments bundle to the new dialog. thisSslCertificateErrorDialog.setArguments(argumentsBundle); + + // Return the new dialog. return thisSslCertificateErrorDialog; } + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Save the components of the SSL error message in class variables. - primaryErrorInt = getArguments().getInt("PrimaryErrorInt"); - urlWithError = getArguments().getString("UrlWithError"); - issuedToCName = getArguments().getString("IssuedToCName"); - issuedToOName = getArguments().getString("IssuedToOName"); - issuedToUName = getArguments().getString("IssuedToUName"); - issuedByCName = getArguments().getString("IssuedByCName"); - issuedByOName = getArguments().getString("IssuedByOName"); - issuedByUName = getArguments().getString("IssuedByUName"); - startDate = getArguments().getString("StartDate"); - endDate = getArguments().getString("EndDate"); - } + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get a handle for the arguments. + Bundle arguments = getArguments(); - // The public interface is used to send information back to the parent activity. - public interface SslCertificateErrorListener { - void onSslErrorCancel(); + // Remove the incorrect lint warning that the arguments might be null. + assert arguments != null; - void onSslErrorProceed(); - } + // Get the variables from the bundle. + int primaryErrorInt = arguments.getInt("primary_error_int"); + String urlWithErrors = arguments.getString("url_with_error"); + String issuedToCName = arguments.getString("issued_to_cname"); + String issuedToOName = arguments.getString("issued_to_oname"); + String issuedToUName = arguments.getString("issued_to_uname"); + String issuedByCName = arguments.getString("issued_by_cname"); + String issuedByOName = arguments.getString("issued_by_oname"); + String issuedByUName = arguments.getString("issued_by_uname"); + String startDate = arguments.getString("start_date"); + String endDate = arguments.getString("end_date"); + long webViewFragmentId = arguments.getLong("webview_fragment_id"); - // `sslCertificateErrorListener` is used in `onAttach` and `onCreateDialog`. - private SslCertificateErrorListener sslCertificateErrorListener; + // Get the current position of this WebView fragment. + int webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId); - // Check to make sure that the parent activity implements the listener. - public void onAttach(Context context) { - super.onAttach(context); + // Get the WebView tab fragment. + WebViewTabFragment webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition); - try { - sslCertificateErrorListener = (SslCertificateErrorListener) context; - } catch(ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement SslCertificateErrorListener"); - } - } + // Get the fragment view. + View fragmentView = webViewTabFragment.getView(); + + // Remove the incorrect lint warning below that the fragment view might be null. + assert fragmentView != null; + + // Get a handle for the current WebView. + NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + + // Get a handle for the SSL error handler. + SslErrorHandler sslErrorHandler = nestedScrollWebView.getSslErrorHandler(); + + // Remove the incorrect lint warning that `getActivity()` might be null. + assert getActivity() != null; - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @SuppressWarnings("deprecation") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { // Get the activity's layout inflater. LayoutInflater layoutInflater = getActivity().getLayoutInflater(); - // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + // Use an alert dialog builder to create the alert dialog. + AlertDialog.Builder dialogBuilder; + + // Get a handle for the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); + + // Get the screenshot and theme preferences. + boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false); + boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false); + + // Set the style and icon according to the theme. + if (darkTheme) { + // Set the style. + dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark); + + // Set the icon. + dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_dark); + } else { + // Set the style. + dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight); + + // Set the icon. + dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_light); + } + + // Set the title. dialogBuilder.setTitle(R.string.ssl_certificate_error); - // The parent view is `null` because it will be assigned by `AlertDialog`. + + // Set the view. The parent view is `null` because it will be assigned by `AlertDialog`. dialogBuilder.setView(layoutInflater.inflate(R.layout.ssl_certificate_error, null)); - // Set an `onClick` listener on the negative button. `null` doesn't do anything extra when the button is pressed. The `Dialog` will automatically close. - dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - sslCertificateErrorListener.onSslErrorCancel(); + // Set a listener on the cancel button. + dialogBuilder.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> { + // Check to make sure the SSL error handler is not null. This might happen if multiple dialogs are displayed at once. + if (sslErrorHandler != null) { + // Cancel the request. + sslErrorHandler.cancel(); + + // Reset the SSL error handler. + nestedScrollWebView.resetSslErrorHandler(); } }); - // Set an `onClick` listener on the positive button. - dialogBuilder.setPositiveButton(R.string.proceed, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - sslCertificateErrorListener.onSslErrorProceed(); + // Set a listener on the proceed button. + dialogBuilder.setPositiveButton(R.string.proceed, (DialogInterface dialog, int which) -> { + // Check to make sure the SSL error handler is not null. This might happen if multiple dialogs are displayed at once. + if (sslErrorHandler != null) { + // Cancel the request. + sslErrorHandler.proceed(); + + // Reset the SSL error handler. + nestedScrollWebView.resetSslErrorHandler(); } }); - // Create an `AlertDialog` from the `AlertDialog.Builder`. + // Create an alert dialog from the alert dialog builder. AlertDialog alertDialog = dialogBuilder.create(); - // We have to show the `AlertDialog` before we can modify the content. + // Disable screenshots if not allowed. + if (!allowScreenshots) { + // Remove the warning below that `getWindow()` might be null. + assert alertDialog.getWindow() != null; + + // Disable screenshots. + alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + + // Get a URI for the URL with errors. + Uri uriWithErrors = Uri.parse(urlWithErrors); + + // Get the IP addresses for the URI. + new GetIpAddresses(getActivity(), alertDialog).execute(uriWithErrors.getHost()); + + // The alert dialog must be shown before the contents can be modified. alertDialog.show(); // Get handles for the `TextViews` - TextView primaryErrorTextView = (TextView) alertDialog.findViewById(R.id.primary_error); - TextView urlTextView = (TextView) alertDialog.findViewById(R.id.url_error_dialog); - TextView issuedToCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_cname_error_dialog); - TextView issuedToONameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_oname_error_dialog); - TextView issuedToUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_uname_error_dialog); - TextView issuedByTextView = (TextView) alertDialog.findViewById(R.id.issued_by_textview); - TextView issuedByCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_cname_error_dialog); - TextView issuedByONameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_oname_error_dialog); - TextView issuedByUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_uname_error_dialog); - TextView validDatesTextView = (TextView) alertDialog.findViewById(R.id.valid_dates_textview); - TextView startDateTextView = (TextView) alertDialog.findViewById(R.id.start_date_error_dialog); - TextView endDateTextView = (TextView) alertDialog.findViewById(R.id.end_date_error_dialog); + TextView primaryErrorTextView = alertDialog.findViewById(R.id.primary_error); + TextView urlTextView = alertDialog.findViewById(R.id.url); + TextView issuedToCNameTextView = alertDialog.findViewById(R.id.issued_to_cname); + TextView issuedToONameTextView = alertDialog.findViewById(R.id.issued_to_oname); + TextView issuedToUNameTextView = alertDialog.findViewById(R.id.issued_to_uname); + TextView issuedByTextView = alertDialog.findViewById(R.id.issued_by_textview); + TextView issuedByCNameTextView = alertDialog.findViewById(R.id.issued_by_cname); + TextView issuedByONameTextView = alertDialog.findViewById(R.id.issued_by_oname); + TextView issuedByUNameTextView = alertDialog.findViewById(R.id.issued_by_uname); + TextView validDatesTextView = alertDialog.findViewById(R.id.valid_dates_textview); + TextView startDateTextView = alertDialog.findViewById(R.id.start_date); + TextView endDateTextView = alertDialog.findViewById(R.id.end_date); // Setup the common strings. String urlLabel = getString(R.string.url_label) + " "; @@ -185,8 +242,8 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment { String startDateLabel = getString(R.string.start_date) + " "; String endDateLabel = getString(R.string.end_date) + " "; - // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text. - SpannableStringBuilder urlStringBuilder = new SpannableStringBuilder(urlLabel + urlWithError); + // Create a spannable string builder for each text view that needs multiple colors of text. + SpannableStringBuilder urlStringBuilder = new SpannableStringBuilder(urlLabel + urlWithErrors); SpannableStringBuilder issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedToCName); SpannableStringBuilder issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedToOName); SpannableStringBuilder issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedToUName); @@ -196,10 +253,19 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment { SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + startDate); SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder((endDateLabel + endDate)); - // Create the `ForegroundColorSpan`. We have to use the deprecated `getColor` until API >= 23. - ForegroundColorSpan blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700)); + // Create a red foreground color span. The deprecated `getResources().getColor` must be used until the minimum API >= 23. ForegroundColorSpan redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700)); + // Create a blue `ForegroundColorSpan`. + ForegroundColorSpan blueColorSpan; + + // Set a blue color span according to the theme. The deprecated `getResources().getColor` must be used until the minimum API >= 23. + if (darkTheme) { + blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_400)); + } else { + blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700)); + } + // Setup the spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. urlStringBuilder.setSpan(blueColorSpan, urlLabel.length(), urlStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); @@ -226,7 +292,7 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment { break; case SslError.SSL_UNTRUSTED: - // Change the `issuesByTextView` text to red. We have to use the deprecated `getColor()` until API >= 23. + // Change the issued by text view text to red. The deprecated `getResources().getColor` must be used until the minimum API >= 23. issuedByTextView.setTextColor(getResources().getColor(R.color.red_a700)); // Change the issued by span color to red. @@ -239,7 +305,7 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment { break; case SslError.SSL_DATE_INVALID: - // Change the `validDatesTextView` text to red. We have to use the deprecated `getColor()` until API >= 23. + // Change the valid dates text view text to red. The deprecated `getResources().getColor` must be used until the minimum API >= 23. validDatesTextView.setTextColor(getResources().getColor(R.color.red_a700)); // Change the date span colors to red. @@ -285,7 +351,105 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment { startDateTextView.setText(startDateStringBuilder); endDateTextView.setText(endDateStringBuilder); - // `onCreateDialog` requires the return of an `AlertDialog`. + // `onCreateDialog` requires the return of an alert dialog. return alertDialog; } + + + // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `SpannableStringBuilder` contains the results. + private static class GetIpAddresses extends AsyncTask { + // The weak references are used to determine if the activity or the alert dialog have disappeared while the AsyncTask is running. + private WeakReference activityWeakReference; + private WeakReference alertDialogWeakReference; + + GetIpAddresses(Activity activity, AlertDialog alertDialog) { + // Populate the weak references. + activityWeakReference = new WeakReference<>(activity); + alertDialogWeakReference = new WeakReference<>(alertDialog); + } + + @Override + protected SpannableStringBuilder doInBackground(String... domainName) { + // Get handles for the activity and the alert dialog. + Activity activity = activityWeakReference.get(); + AlertDialog alertDialog = alertDialogWeakReference.get(); + + // Abort if the activity or the dialog is gone. + if ((activity == null) || (activity.isFinishing()) || (alertDialog == null)) { + return new SpannableStringBuilder(); + } + + // Initialize an IP address string builder. + StringBuilder ipAddresses = new StringBuilder(); + + // Get an array with the IP addresses for the host. + try { + // Get an array with all the IP addresses for the domain. + InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]); + + // Add each IP address to the string builder. + for (InetAddress inetAddress : inetAddressesArray) { + if (ipAddresses.length() == 0) { // This is the first IP address. + // Add the IP Address to the string builder. + ipAddresses.append(inetAddress.getHostAddress()); + } else { // This is not the first IP address. + // Add a line break to the string builder first. + ipAddresses.append("\n"); + + // Add the IP address to the string builder. + ipAddresses.append(inetAddress.getHostAddress()); + } + } + } catch (UnknownHostException exception) { + // Do nothing. + } + + // Set the label. + String ipAddressesLabel = activity.getString(R.string.ip_addresses) + " "; + + // Create a spannable string builder. + SpannableStringBuilder ipAddressesStringBuilder = new SpannableStringBuilder(ipAddressesLabel + ipAddresses); + + // Get a handle for the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); + + // Get the screenshot and theme preferences. + boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false); + + // Create a blue foreground color span. + ForegroundColorSpan blueColorSpan; + + // Set the blue color span according to the theme. The deprecated `getColor()` must be used until the minimum API >= 23. + if (darkTheme) { + blueColorSpan = new ForegroundColorSpan(activity.getResources().getColor(R.color.blue_400)); + } else { + blueColorSpan = new ForegroundColorSpan(activity.getResources().getColor(R.color.blue_700)); + } + + // Set the string builder to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length(), ipAddressesStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // Return the formatted string. + return ipAddressesStringBuilder; + } + + // `onPostExecute()` operates on the UI thread. + @Override + protected void onPostExecute(SpannableStringBuilder ipAddresses) { + // Get handles for the activity and the alert dialog. + Activity activity = activityWeakReference.get(); + AlertDialog alertDialog = alertDialogWeakReference.get(); + + // Abort if the activity or the alert dialog is gone. + if ((activity == null) || (activity.isFinishing()) || (alertDialog == null)) { + return; + } + + // Get a handle for the IP addresses text view. + TextView ipAddressesTextView = alertDialog.findViewById(R.id.ip_addresses); + + // Populate the IP addresses text view. + ipAddressesTextView.setText(ipAddresses); + } + } }