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=76732ff270e4bf7bd15fa09d044218205dd6d080;hp=0416d160a5e8717bf6182fb23df9ec090629c983;hb=6b4312dc0c2d6cb059a0fbe6d4e7cd9317db34b6;hpb=61a76e491469916f2f30aebb47b98cda7cceb557 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 0416d160..76732ff2 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,39 +20,53 @@ 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.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. +// `AppCompatDialogFragment` is used instead of `DialogFragment` to avoid an error on API <=22. import android.support.v7.app.AppCompatDialogFragment; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; +import android.view.WindowManager; import android.widget.TextView; import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.activities.MainWebViewActivity; +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 { + // `sslCertificateErrorListener` is used in `onAttach` and `onCreateDialog`. + private SslCertificateErrorListener sslCertificateErrorListener; + + // The public interface is used to send information back to the parent activity. + public interface SslCertificateErrorListener { + void onSslErrorCancel(); - private String primaryError; - 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; + void onSslErrorProceed(); + } + + public void onAttach(Context context) { + // Run the default commands. + super.onAttach(context); + + // Get a handle for `SslCertificateErrorListener` from the launching context. + sslCertificateErrorListener = (SslCertificateErrorListener) context; + } public static SslCertificateErrorDialog displayDialog(SslError error) { // Get the various components of the SSL error message. @@ -87,119 +101,98 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment { return thisSslCertificateErrorDialog; } + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @SuppressWarnings("deprecation") @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Save the components of the SSL error message in class variables. - 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"); - - // Get the appropriate string for `primaryError. + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Remove the incorrect lint warning that `getArguments()` might be null. + assert getArguments() != null; + + // Get the components of the SSL error message from the bundle. int primaryErrorInt = getArguments().getInt("PrimaryErrorInt"); - switch (primaryErrorInt) { - case SslError.SSL_NOTYETVALID: - primaryError = getString(R.string.future_certificate); - break; + String urlWithErrors = getArguments().getString("UrlWithError"); + String issuedToCName = getArguments().getString("IssuedToCName"); + String issuedToOName = getArguments().getString("IssuedToOName"); + String issuedToUName = getArguments().getString("IssuedToUName"); + String issuedByCName = getArguments().getString("IssuedByCName"); + String issuedByOName = getArguments().getString("IssuedByOName"); + String issuedByUName = getArguments().getString("IssuedByUName"); + String startDate = getArguments().getString("StartDate"); + String endDate = getArguments().getString("EndDate"); + + // Remove the incorrect lint warning that `getActivity()` might be null. + assert getActivity() != null; - case SslError.SSL_EXPIRED: - primaryError = getString(R.string.expired_certificate); - break; + // Get the activity's layout inflater. + LayoutInflater layoutInflater = getActivity().getLayoutInflater(); - case SslError.SSL_IDMISMATCH: - primaryError = getString(R.string.cn_mismatch); - break; + // Use an alert dialog builder to create the alert dialog. + AlertDialog.Builder dialogBuilder; - case SslError.SSL_UNTRUSTED: - primaryError = getString(R.string.untrusted); - break; + // Set the style and icon according to the theme. + if (MainWebViewActivity.darkTheme) { + // Set the style. + dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogDark); - case SslError.SSL_DATE_INVALID: - primaryError = getString(R.string.invalid_date); - break; + // Set the icon. + dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_dark); + } else { + // Set the style. + dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowserAlertDialogLight); - case SslError.SSL_INVALID: - primaryError = getString(R.string.invalid_certificate); - break; + // Set the icon. + dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_light); } - } - // The public interface is used to send information back to the parent activity. - public interface SslCertificateErrorListener { - void onSslErrorCancel(); + // Set the title. + dialogBuilder.setTitle(R.string.ssl_certificate_error); - void onSslErrorProceed(); - } + // 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)); - // `sslCertificateErrorListener` is used in `onAttach` and `onCreateDialog`. - private SslCertificateErrorListener sslCertificateErrorListener; + // Set a listener on the negative button. + dialogBuilder.setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> sslCertificateErrorListener.onSslErrorCancel()); - // Check to make sure that the parent activity implements the listener. - public void onAttach(Context context) { - super.onAttach(context); + // Set a listener on the positive button. + dialogBuilder.setPositiveButton(R.string.proceed, (DialogInterface dialog, int which) -> sslCertificateErrorListener.onSslErrorProceed()); - try { - sslCertificateErrorListener = (SslCertificateErrorListener) context; - } catch(ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement SslCertificateErrorListener"); - } - } - - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @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); - dialogBuilder.setTitle(R.string.ssl_certificate_error); - // The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(layoutInflater.inflate(R.layout.ssl_certificate_error, null)); + // Create an alert dialog from the alert dialog builder. + AlertDialog alertDialog = dialogBuilder.create(); - // 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(); - } - }); + // Disable screenshots if not allowed. + if (!MainWebViewActivity.allowScreenshots) { + // Remove the warning below that `getWindow()` might be null. + assert alertDialog.getWindow() != null; - // 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(); - } - }); + // Disable screenshots. + alertDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + // Get a URI for the URL with errors. + Uri uriWithErrors = Uri.parse(urlWithErrors); - // Create an `AlertDialog` from the `AlertDialog.Builder`. - AlertDialog alertDialog = dialogBuilder.create(); + // Get the IP addresses for the URI. + new GetIpAddresses(getActivity(), alertDialog).execute(uriWithErrors.getHost()); - // We have to show the `AlertDialog` before we can modify the content. + // 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 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 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) + " "; @@ -209,8 +202,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); @@ -220,8 +213,20 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment { SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + startDate); SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder((endDateLabel + endDate)); - // Create a blue `ForegroundColorSpan`. We have to use the deprecated `getColor` until API >= 23. - @SuppressWarnings("deprecation") 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. + @SuppressWarnings("deprecation") 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 (MainWebViewActivity.darkTheme) { + //noinspection deprecation + blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_400)); + } else { + //noinspection deprecation + 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); @@ -234,9 +239,70 @@ public class SslCertificateErrorDialog extends AppCompatDialogFragment { startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + // Initialize `primaryErrorString`. + String primaryErrorString = ""; + + // Highlight the primary error in red and store the primary error string in `primaryErrorString`. + switch (primaryErrorInt) { + case SslError.SSL_IDMISMATCH: + // Change the URL span colors to red. + urlStringBuilder.setSpan(redColorSpan, urlLabel.length(), urlStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // Store the primary error string. + primaryErrorString = getString(R.string.cn_mismatch); + break; + + case SslError.SSL_UNTRUSTED: + // 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. + issuedByCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByONameStringBuilder.setSpan(redColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByUNameStringBuilder.setSpan(redColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // Store the primary error string. + primaryErrorString = getString(R.string.untrusted); + break; + + case SslError.SSL_DATE_INVALID: + // 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. + startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // Store the primary error string. + primaryErrorString = getString(R.string.invalid_date); + break; + + case SslError.SSL_NOTYETVALID: + // Change the start date span color to red. + startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // Store the primary error string. + primaryErrorString = getString(R.string.future_certificate); + break; + + case SslError.SSL_EXPIRED: + // Change the end date span color to red. + endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // Store the primary error string. + primaryErrorString = getString(R.string.expired_certificate); + break; + + case SslError.SSL_INVALID: + // Store the primary error string. + primaryErrorString = getString(R.string.invalid_certificate); + break; + } + // Display the strings. - primaryErrorTextView.setText(primaryError); + primaryErrorTextView.setText(primaryErrorString); urlTextView.setText(urlStringBuilder); issuedToCNameTextView.setText(issuedToCNameStringBuilder); issuedToONameTextView.setText(issuedToONameStringBuilder); @@ -247,7 +313,101 @@ 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); + + // 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 (MainWebViewActivity.darkTheme) { + //noinspection deprecation + blueColorSpan = new ForegroundColorSpan(activity.getResources().getColor(R.color.blue_400)); + } else { + //noinspection deprecation + 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); + } + } }