]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blobdiff - app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt
Migrate the rest of the dialogs to Kotlin. https://redmine.stoutner.com/issues/683
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / SslCertificateErrorDialog.kt
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.kt
new file mode 100644 (file)
index 0000000..139e657
--- /dev/null
@@ -0,0 +1,441 @@
+/*
+ * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser 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.
+ *
+ * Privacy Browser 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 Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.app.Dialog
+import android.content.DialogInterface
+import android.content.res.Configuration
+import android.net.Uri
+import android.net.http.SslError
+import android.os.AsyncTask
+import android.os.Bundle
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.style.ForegroundColorSpan
+import android.view.WindowManager
+import android.widget.TextView
+
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import androidx.preference.PreferenceManager
+
+import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.activities.MainWebViewActivity
+import com.stoutner.privacybrowser.views.NestedScrollWebView
+
+import java.lang.ref.WeakReference
+import java.net.InetAddress
+import java.net.UnknownHostException
+import java.text.DateFormat
+
+// Define the class constants.
+private const val PRIMARY_ERROR_INT = "primary_error_int"
+private const val URL_WITH_ERRORS = "url_with_errors"
+private const val ISSUED_TO_CNAME = "issued_to_cname"
+private const val ISSUED_TO_ONAME = "issued_to_oname"
+private const val ISSUED_TO_UNAME = "issued_to_uname"
+private const val ISSUED_BY_CNAME = "issued_by_cname"
+private const val ISSUED_BY_ONAME = "issued_by_oname"
+private const val ISSUED_BY_UNAME = "issued_by_uname"
+private const val START_DATE = "start_date"
+private const val END_DATE = "end_date"
+private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
+
+class SslCertificateErrorDialog : DialogFragment() {
+    companion object {
+        // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
+        @JvmStatic
+        fun displayDialog(sslError: SslError, webViewFragmentId: Long): SslCertificateErrorDialog {
+            // Get the various components of the SSL error message.
+            val primaryErrorInt = sslError.primaryError
+            val urlWithErrors = sslError.url
+            val sslCertificate = sslError.certificate
+            val issuedToCName = sslCertificate.issuedTo.cName
+            val issuedToOName = sslCertificate.issuedTo.oName
+            val issuedToUName = sslCertificate.issuedTo.uName
+            val issuedByCName = sslCertificate.issuedBy.cName
+            val issuedByOName = sslCertificate.issuedBy.oName
+            val issuedByUName = sslCertificate.issuedBy.uName
+            val startDate = sslCertificate.validNotBeforeDate
+            val endDate = sslCertificate.validNotAfterDate
+
+            // Create an arguments bundle.
+            val argumentsBundle = Bundle()
+
+            // Store the SSL error message components in the bundle.
+            argumentsBundle.putInt(PRIMARY_ERROR_INT, primaryErrorInt)
+            argumentsBundle.putString(URL_WITH_ERRORS, urlWithErrors)
+            argumentsBundle.putString(ISSUED_TO_CNAME, issuedToCName)
+            argumentsBundle.putString(ISSUED_TO_ONAME, issuedToOName)
+            argumentsBundle.putString(ISSUED_TO_UNAME, issuedToUName)
+            argumentsBundle.putString(ISSUED_BY_CNAME, issuedByCName)
+            argumentsBundle.putString(ISSUED_BY_ONAME, issuedByOName)
+            argumentsBundle.putString(ISSUED_BY_UNAME, issuedByUName)
+            argumentsBundle.putString(START_DATE, DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
+            argumentsBundle.putString(END_DATE, DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
+            argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
+
+            // Create a new instance of the SSL certificate error dialog.
+            val thisSslCertificateErrorDialog = SslCertificateErrorDialog()
+
+            // Add the arguments bundle to the new dialog.
+            thisSslCertificateErrorDialog.arguments = argumentsBundle
+
+            // Return the new dialog.
+            return thisSslCertificateErrorDialog
+        }
+    }
+
+    // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog.
+    @SuppressLint("InflateParams")
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        // Get the variables from the bundle.
+        val primaryErrorInt = requireArguments().getInt(PRIMARY_ERROR_INT)
+        val urlWithErrors = requireArguments().getString(URL_WITH_ERRORS)
+        val issuedToCName = requireArguments().getString(ISSUED_TO_CNAME)
+        val issuedToOName = requireArguments().getString(ISSUED_TO_ONAME)
+        val issuedToUName = requireArguments().getString(ISSUED_TO_UNAME)
+        val issuedByCName = requireArguments().getString(ISSUED_BY_CNAME)
+        val issuedByOName = requireArguments().getString(ISSUED_BY_ONAME)
+        val issuedByUName = requireArguments().getString(ISSUED_BY_UNAME)
+        val startDate = requireArguments().getString(START_DATE)
+        val endDate = requireArguments().getString(END_DATE)
+        val webViewFragmentId = requireArguments().getLong(WEBVIEW_FRAGMENT_ID)
+
+        // Get the current position of this WebView fragment.
+        val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(webViewFragmentId)
+
+        // Get the WebView tab fragment.
+        val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
+
+        // Get the fragment view.
+        val fragmentView = webViewTabFragment.requireView()
+
+        // Get a handle for the current WebView.
+        val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)
+
+        // Get a handle for the SSL error handler.
+        val sslErrorHandler = nestedScrollWebView.sslErrorHandler
+
+        // Use an alert dialog builder to create the alert dialog.
+        val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
+
+        // Get the current theme status.
+        val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+
+        // Set the icon according to the theme.
+        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+            dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_day)
+        } else {
+            dialogBuilder.setIcon(R.drawable.ssl_certificate_enabled_night)
+        }
+
+        // Set the title.
+        dialogBuilder.setTitle(R.string.ssl_certificate_error)
+
+        // Set the view.  The parent view is null because it will be assigned by the alert dialog.
+        dialogBuilder.setView(layoutInflater.inflate(R.layout.ssl_certificate_error, null))
+
+        // Set a listener on the cancel button.
+        dialogBuilder.setNegativeButton(R.string.cancel) { _: DialogInterface?, _: Int ->
+            // 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 a listener on the proceed button.
+        dialogBuilder.setPositiveButton(R.string.proceed) { _: DialogInterface?, _: Int ->
+            // Check to make sure the SSL error handler is not null.  This might happen if multiple dialogs are displayed at once.
+            if (sslErrorHandler != null) {
+                // Proceed to the website.
+                sslErrorHandler.proceed()
+
+                // Reset the SSL error handler.
+                nestedScrollWebView.resetSslErrorHandler()
+            }
+        }
+
+        // Create an alert dialog from the builder.
+        val alertDialog = dialogBuilder.create()
+
+        // Get a handle for the shared preferences.
+        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+
+        // Get the screenshot preference.
+        val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
+
+        // Disable screenshots if not allowed.
+        if (!allowScreenshots) {
+            // Disable screenshots.
+            alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+        }
+
+        // Get a URI for the URL with errors.
+        val uriWithErrors = Uri.parse(urlWithErrors)
+
+        // Get the IP addresses for the URI.
+        GetIpAddresses(requireActivity(), alertDialog).execute(uriWithErrors.host)
+
+        // The alert dialog must be shown before the contents can be modified.
+        alertDialog.show()
+
+        // Get handles for the views.
+        val primaryErrorTextView = alertDialog.findViewById<TextView>(R.id.primary_error)!!
+        val urlTextView = alertDialog.findViewById<TextView>(R.id.url)!!
+        val issuedToCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_cname)!!
+        val issuedToONameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_oname)!!
+        val issuedToUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_uname)!!
+        val issuedByTextView = alertDialog.findViewById<TextView>(R.id.issued_by_textview)!!
+        val issuedByCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_cname)!!
+        val issuedByONameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_oname)!!
+        val issuedByUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_uname)!!
+        val validDatesTextView = alertDialog.findViewById<TextView>(R.id.valid_dates_textview)!!
+        val startDateTextView = alertDialog.findViewById<TextView>(R.id.start_date)!!
+        val endDateTextView = alertDialog.findViewById<TextView>(R.id.end_date)!!
+
+        // Setup the common strings.
+        val urlLabel = getString(R.string.url_label) + "  "
+        val cNameLabel = getString(R.string.common_name) + "  "
+        val oNameLabel = getString(R.string.organization) + "  "
+        val uNameLabel = getString(R.string.organizational_unit) + "  "
+        val startDateLabel = getString(R.string.start_date) + "  "
+        val endDateLabel = getString(R.string.end_date) + "  "
+
+        // Create a spannable string builder for each text view that needs multiple colors of text.
+        val urlStringBuilder = SpannableStringBuilder(urlLabel + urlWithErrors)
+        val issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedToCName)
+        val issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedToOName)
+        val issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedToUName)
+        val issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedByCName)
+        val issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedByOName)
+        val issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedByUName)
+        val startDateStringBuilder = SpannableStringBuilder(startDateLabel + startDate)
+        val endDateStringBuilder = SpannableStringBuilder(endDateLabel + endDate)
+
+        // Define the color spans.
+        val blueColorSpan: ForegroundColorSpan
+        val redColorSpan: ForegroundColorSpan
+
+        // Set the color spans according to the theme.  The deprecated `getColor()` must be used until the minimum API >= 23.
+        if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+            @Suppress("DEPRECATION")
+            blueColorSpan = ForegroundColorSpan(resources.getColor(R.color.blue_700))
+            @Suppress("DEPRECATION")
+            redColorSpan = ForegroundColorSpan(resources.getColor(R.color.red_a700))
+        } else {
+            @Suppress("DEPRECATION")
+            blueColorSpan = ForegroundColorSpan(resources.getColor(R.color.violet_700))
+            @Suppress("DEPRECATION")
+            redColorSpan = ForegroundColorSpan(resources.getColor(R.color.red_900))
+        }
+
+        // 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)
+        issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+        issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+        issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+        issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+        issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+        startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+        endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+
+        // Define the primary error string.
+        var primaryErrorString = ""
+
+        // Highlight the primary error in red and store it in the primary error string.
+        when (primaryErrorInt) {
+            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)
+            }
+
+            SslError.SSL_UNTRUSTED -> {
+                // Change the issued by text view text to red.  The deprecated `getColor()` must be used until the minimum API >= 23.
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+                    @Suppress("DEPRECATION")
+                    issuedByTextView.setTextColor(resources.getColor(R.color.red_a700))
+                } else {
+                    @Suppress("DEPRECATION")
+                    issuedByTextView.setTextColor(resources.getColor(R.color.red_900))
+                }
+
+                // 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)
+            }
+
+            SslError.SSL_DATE_INVALID -> {
+                // Change the valid dates text view text to red.  The deprecated `getColor()` must be used until the minimum API >= 23.
+                if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+                    @Suppress("DEPRECATION")
+                    validDatesTextView.setTextColor(resources.getColor(R.color.red_a700))
+                } else {
+                    @Suppress("DEPRECATION")
+                    validDatesTextView.setTextColor(resources.getColor(R.color.red_900))
+                }
+
+                // 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)
+            }
+
+            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)
+            }
+
+            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)
+            }
+
+            SslError.SSL_INVALID ->
+                // Store the primary error string.
+                primaryErrorString = getString(R.string.invalid_certificate)
+        }
+
+
+        // Display the strings.
+        primaryErrorTextView.text = primaryErrorString
+        urlTextView.text = urlStringBuilder
+        issuedToCNameTextView.text = issuedToCNameStringBuilder
+        issuedToONameTextView.text = issuedToONameStringBuilder
+        issuedToUNameTextView.text = issuedToUNameStringBuilder
+        issuedByCNameTextView.text = issuedByCNameStringBuilder
+        issuedByONameTextView.text = issuedByONameStringBuilder
+        issuedByUNameTextView.text = issuedByUNameStringBuilder
+        startDateTextView.text = startDateStringBuilder
+        endDateTextView.text = endDateStringBuilder
+
+        // Return the 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 class GetIpAddresses constructor(activity: Activity, alertDialog: AlertDialog) : AsyncTask<String, Void?, SpannableStringBuilder>() {
+        // Define the weak references.
+        private val activityWeakReference: WeakReference<Activity> = WeakReference(activity)
+        private val alertDialogWeakReference: WeakReference<AlertDialog> = WeakReference(alertDialog)
+
+        override fun doInBackground(vararg domainName: String): SpannableStringBuilder {
+            // Get handles for the activity and the alert dialog.
+            val activity = activityWeakReference.get()
+            val alertDialog = alertDialogWeakReference.get()
+
+            // Abort if the activity or the dialog is gone.
+            if (activity == null || activity.isFinishing || alertDialog == null) {
+                return SpannableStringBuilder()
+            }
+
+            // Initialize an IP address string builder.
+            val ipAddresses = StringBuilder()
+
+            // Get an array with the IP addresses for the host.
+            try {
+                // Get an array with all the IP addresses for the domain.
+                val inetAddressesArray = InetAddress.getAllByName(domainName[0])
+
+                // Add each IP address to the string builder.
+                for (inetAddress in inetAddressesArray) {
+                    // Check to see if this is not the first IP address.
+                    if (ipAddresses.isNotEmpty()) {
+                        // Add a line break to the string builder first.
+                        ipAddresses.append("\n")
+                    }
+
+                    // Add the IP Address to the string builder.
+                    ipAddresses.append(inetAddress.hostAddress)
+                }
+            } catch (exception: UnknownHostException) {
+                // Do nothing.
+            }
+
+            // Set the label.
+            val ipAddressesLabel = activity.getString(R.string.ip_addresses) + "  "
+
+            // Create a spannable string builder.
+            val ipAddressesStringBuilder = SpannableStringBuilder(ipAddressesLabel + ipAddresses)
+
+            // Create a blue foreground color span.
+            val blueColorSpan: ForegroundColorSpan
+
+            // Get the current theme status.
+            val currentThemeStatus = activity.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+
+            // Set the blue color span according to the theme.  The deprecated `getColor()` must be used until the minimum API >= 23.
+            blueColorSpan = if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
+                @Suppress("DEPRECATION")
+                ForegroundColorSpan(activity.resources.getColor(R.color.blue_700))
+            } else {
+                @Suppress("DEPRECATION")
+                ForegroundColorSpan(activity.resources.getColor(R.color.violet_500))
+            }
+
+            // 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 fun onPostExecute(ipAddresses: SpannableStringBuilder) {
+            // Get handles for the activity and the alert dialog.
+            val activity = activityWeakReference.get()
+            val 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.
+            val ipAddressesTextView = alertDialog.findViewById<TextView>(R.id.ip_addresses)!!
+
+            // Populate the IP addresses text view.
+            ipAddressesTextView.text = ipAddresses
+        }
+    }
+}
\ No newline at end of file