]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/commitdiff
Display SSL information in View Headers. https://redmine.stoutner.com/issues/706
authorSoren Stoutner <soren@stoutner.com>
Tue, 24 Oct 2023 19:21:22 +0000 (12:21 -0700)
committerSoren Stoutner <soren@stoutner.com>
Tue, 24 Oct 2023 19:21:22 +0000 (12:21 -0700)
15 files changed:
app/src/main/java/com/stoutner/privacybrowser/activities/ViewHeadersActivity.kt
app/src/main/java/com/stoutner/privacybrowser/backgroundtasks/GetHeadersBackgroundTask.kt
app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewHeadersDetailDialog.kt [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/viewmodelfactories/ViewHeadersFactory.kt
app/src/main/java/com/stoutner/privacybrowser/viewmodels/HeadersViewModel.kt
app/src/main/res/layout/view_headers_bottom_appbar.xml
app/src/main/res/layout/view_headers_top_appbar.xml
app/src/main/res/values-de/strings.xml
app/src/main/res/values-es/strings.xml
app/src/main/res/values-fr/strings.xml
app/src/main/res/values-pt-rBR/strings.xml
app/src/main/res/values-ru/strings.xml
app/src/main/res/values-tr/strings.xml
app/src/main/res/values-zh-rCN/strings.xml
app/src/main/res/values/strings.xml

index 2f0aeb0b143d0e4fa596c1d1ec03070e37e76eee..c8878c5fa772a4e53b824155ad4eb6ccbdfe8a3a 100644 (file)
@@ -30,6 +30,7 @@ import android.view.View
 import android.view.View.OnFocusChangeListener
 import android.view.WindowManager
 import android.view.inputmethod.InputMethodManager
+import android.widget.Button
 import android.widget.EditText
 import android.widget.ProgressBar
 import android.widget.TextView
@@ -46,7 +47,10 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
 import com.google.android.material.snackbar.Snackbar
 
 import com.stoutner.privacybrowser.R
+import com.stoutner.privacybrowser.dialogs.AVAILABLE_CIPHERS
+import com.stoutner.privacybrowser.dialogs.SSL_CERTIFICATE
 import com.stoutner.privacybrowser.dialogs.AboutViewHeadersDialog
+import com.stoutner.privacybrowser.dialogs.ViewHeadersDetailDialog
 import com.stoutner.privacybrowser.dialogs.UntrustedSslCertificateDialog
 import com.stoutner.privacybrowser.dialogs.UntrustedSslCertificateDialog.UntrustedSslCertificateListener
 import com.stoutner.privacybrowser.helpers.ProxyHelper
@@ -59,13 +63,20 @@ const val USER_AGENT = "user_agent"
 
 class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener {
     // Declare the class variables.
+    private lateinit var appliedCipherString: String
+    private lateinit var availableCiphersString: String
     private lateinit var headersViewModel: HeadersViewModel
     private lateinit var initialGrayColorSpan: ForegroundColorSpan
     private lateinit var finalGrayColorSpan: ForegroundColorSpan
     private lateinit var redColorSpan: ForegroundColorSpan
+    private lateinit var sslCertificateString: String
 
     // Declare the class views.
     private lateinit var urlEditText: EditText
+    private lateinit var sslInformationTitleTextView: TextView
+    private lateinit var sslInformationTextView: TextView
+    private lateinit var ciphersButton: Button
+    private lateinit var certificateButton: Button
     private lateinit var requestHeadersTitleTextView: TextView
     private lateinit var requestHeadersTextView: TextView
     private lateinit var responseMessageTitleTextView: TextView
@@ -122,6 +133,10 @@ class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener
         urlEditText = findViewById(R.id.url_edittext)
         val progressBar = findViewById<ProgressBar>(R.id.progress_bar)
         val swipeRefreshLayout = findViewById<SwipeRefreshLayout>(R.id.swiperefreshlayout)
+        sslInformationTitleTextView = findViewById(R.id.ssl_information_title_textview)
+        sslInformationTextView = findViewById(R.id.ssl_information_textview)
+        ciphersButton = findViewById(R.id.ciphers_button)
+        certificateButton = findViewById(R.id.certificate_button)
         requestHeadersTitleTextView = findViewById(R.id.request_headers_title_textview)
         requestHeadersTextView = findViewById(R.id.request_headers_textview)
         responseMessageTitleTextView = findViewById(R.id.response_message_title_textview)
@@ -244,7 +259,7 @@ class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener
         updateLayout(currentUrl)
 
         // Instantiate the view headers factory.
-        val viewHeadersFactory: ViewModelProvider.Factory = ViewHeadersFactory(currentUrl, userAgent, localesStringBuilder.toString(), proxy, contentResolver, MainWebViewActivity.executorService)
+        val viewHeadersFactory: ViewModelProvider.Factory = ViewHeadersFactory(application, currentUrl, userAgent, localesStringBuilder.toString(), proxy, contentResolver, MainWebViewActivity.executorService)
 
         // Instantiate the headers view model.
         headersViewModel = ViewModelProvider(this, viewHeadersFactory)[HeadersViewModel::class.java]
@@ -252,16 +267,22 @@ class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener
         // Create a headers observer.
         headersViewModel.observeHeaders().observe(this) { headersStringArray: Array<SpannableStringBuilder> ->
             // Populate the text views.  This can take a long time, and freezes the user interface, if the response body is particularly large.
-            requestHeadersTextView.text = headersStringArray[0]
-            responseMessageTextView.text = headersStringArray[1]
-            responseHeadersTextView.text = headersStringArray[2]
-            responseBodyTextView.text = headersStringArray[3]
+            sslInformationTextView.text = headersStringArray[0]
+            requestHeadersTextView.text = headersStringArray[4]
+            responseMessageTextView.text = headersStringArray[5]
+            responseHeadersTextView.text = headersStringArray[6]
+            responseBodyTextView.text = headersStringArray[7]
+
+            // Populate the dialog strings.
+            appliedCipherString = headersStringArray[1].toString()
+            availableCiphersString = headersStringArray[2].toString()
+            sslCertificateString = headersStringArray[3].toString()
 
             // Hide the progress bar.
             progressBar.isIndeterminate = false
             progressBar.visibility = View.GONE
 
-            //Stop the swipe to refresh indicator if it is running
+            // Stop the swipe to refresh indicator if it is running
             swipeRefreshLayout.isRefreshing = false
         }
 
@@ -364,9 +385,31 @@ class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener
         headersViewModel.updateHeaders(urlEditText.text.toString(), true)
     }
 
+    // The view parameter cannot be removed because it is called from the layout onClick.
+    fun showCertificate(@Suppress("UNUSED_PARAMETER")view: View) {
+        // Instantiate an SSL certificate dialog.
+        val sslCertificateDialogFragment= ViewHeadersDetailDialog.displayDialog(SSL_CERTIFICATE, sslCertificateString)
+
+        // Show the dialog.
+        sslCertificateDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate))
+    }
+
+    // The view parameter cannot be removed because it is called from the layout onClick.
+    fun showCiphers(@Suppress("UNUSED_PARAMETER")view: View) {
+        // Instantiate an SSL certificate dialog.
+        val ciphersDialogFragment= ViewHeadersDetailDialog.displayDialog(AVAILABLE_CIPHERS, availableCiphersString, appliedCipherString)
+
+        // Show the dialog.
+        ciphersDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate))
+    }
+
     private fun updateLayout(urlString: String) {
         if (urlString.startsWith("content://")) {  // This is a content URL.
-            // Hide the unused text views.
+            // Hide the unused views.
+            sslInformationTitleTextView.visibility = View.GONE
+            sslInformationTextView.visibility = View.GONE
+            ciphersButton.visibility = View.GONE
+            certificateButton.visibility = View.GONE
             requestHeadersTitleTextView.visibility = View.GONE
             requestHeadersTextView.visibility = View.GONE
             responseMessageTitleTextView.visibility = View.GONE
@@ -376,7 +419,22 @@ class ViewHeadersActivity: AppCompatActivity(), UntrustedSslCertificateListener
             responseHeadersTitleTextView.setText(R.string.content_metadata)
             responseBodyTitleTextView.setText(R.string.content_data)
         } else {  // This is not a content URL.
-            // Show the views.
+            // Set the status if the the SSL information views.
+            if (urlString.startsWith("http://")) {  // This is an HTTP URL.
+                // Hide the SSL information views.
+                sslInformationTitleTextView.visibility = View.GONE
+                sslInformationTextView.visibility = View.GONE
+                ciphersButton.visibility = View.GONE
+                certificateButton.visibility = View.GONE
+            } else {  // This is not an HTTP URL.
+                // Show the SSL information views.
+                sslInformationTitleTextView.visibility = View.VISIBLE
+                sslInformationTextView.visibility = View.VISIBLE
+                ciphersButton.visibility = View.VISIBLE
+                certificateButton.visibility = View.VISIBLE
+            }
+
+            // Show the other views.
             requestHeadersTitleTextView.visibility = View.VISIBLE
             requestHeadersTextView.visibility = View.VISIBLE
             responseMessageTitleTextView.visibility = View.VISIBLE
index 030ce4c6d3a6cd18b061cf57fb6f64aca32e7a9b..634e4f6529463a4a7aa72b42005d5ffbc2babfbc 100644 (file)
@@ -20,6 +20,7 @@
 package com.stoutner.privacybrowser.backgroundtasks
 
 import android.annotation.SuppressLint
+import android.app.Application
 import android.content.ContentResolver
 import android.graphics.Typeface
 import android.net.Uri
@@ -28,6 +29,7 @@ import android.text.Spanned
 import android.text.style.StyleSpan
 import android.webkit.CookieManager
 
+import com.stoutner.privacybrowser.R
 import com.stoutner.privacybrowser.viewmodels.HeadersViewModel
 
 import java.io.BufferedInputStream
@@ -53,15 +55,23 @@ import javax.net.ssl.X509TrustManager
 
 
 class GetHeadersBackgroundTask {
-    fun acquire(urlString: String, userAgent: String, localeString: String, proxy: Proxy, contentResolver: ContentResolver, headersViewModel: HeadersViewModel, ignoreSslErrors: Boolean):
+    fun acquire(application: Application, urlString: String, userAgent: String, localeString: String, proxy: Proxy, contentResolver: ContentResolver, headersViewModel: HeadersViewModel, ignoreSslErrors: Boolean):
             Array<SpannableStringBuilder> {
 
         // Initialize the spannable string builders.
+        val sslInformationBuilder = SpannableStringBuilder()
+        val appliedCipherBuilder = SpannableStringBuilder()
+        val availableCiphersBuilder = SpannableStringBuilder()
+        val sslCertificateBuilder = SpannableStringBuilder()
         val requestHeadersBuilder = SpannableStringBuilder()
         val responseMessageBuilder = SpannableStringBuilder()
         val responseHeadersBuilder = SpannableStringBuilder()
         val responseBodyBuilder = SpannableStringBuilder()
 
+        // Get the colon string.
+        val colonString = application.getString(R.string.colon)
+        val newLineString = System.getProperty("line.separator")
+
         if (urlString.startsWith("content://")) {  // This is a content URL.
             // Attempt to read the content data.  Return an error if this fails.
             try {
@@ -78,11 +88,11 @@ class GetHeadersBackgroundTask {
                 for (i in 0 until contentCursor.columnCount) {
                     // Add a new line if this is not the first entry.
                     if (i > 0)
-                        responseHeadersBuilder.append(System.getProperty("line.separator"))
+                        responseHeadersBuilder.append(newLineString)
 
                     // Add each header to the string builder.
                     responseHeadersBuilder.append(contentCursor.getColumnName(i), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                    responseHeadersBuilder.append(":  ")
+                    responseHeadersBuilder.append(colonString)
                     responseHeadersBuilder.append(contentCursor.getString(i))
                 }
 
@@ -121,7 +131,7 @@ class GetHeadersBackgroundTask {
 
                 // Add the `Host` header to the string builder and format the text.
                 requestHeadersBuilder.append("Host", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                requestHeadersBuilder.append(":  ")
+                requestHeadersBuilder.append(colonString)
                 requestHeadersBuilder.append(url.host)
 
 
@@ -129,27 +139,29 @@ class GetHeadersBackgroundTask {
                 httpUrlConnection.setRequestProperty("Connection", "keep-alive")
 
                 // Add the `Connection` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"))
+                requestHeadersBuilder.append(newLineString)
                 requestHeadersBuilder.append("Connection", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                requestHeadersBuilder.append(":  keep-alive")
+                requestHeadersBuilder.append(colonString)
+                requestHeadersBuilder.append("keep-alive")
 
 
                 // Set the `Upgrade-Insecure-Requests` header property.
                 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1")
 
                 // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"))
+                requestHeadersBuilder.append(newLineString)
                 requestHeadersBuilder.append("Upgrade-Insecure-Requests", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                requestHeadersBuilder.append(":  1")
+                requestHeadersBuilder.append(colonString)
+                requestHeadersBuilder.append("1")
 
 
                 // Set the `User-Agent` header property.
                 httpUrlConnection.setRequestProperty("User-Agent", userAgent)
 
                 // Add the `User-Agent` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"))
+                requestHeadersBuilder.append(newLineString)
                 requestHeadersBuilder.append("User-Agent", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                requestHeadersBuilder.append(":  ")
+                requestHeadersBuilder.append(colonString)
                 requestHeadersBuilder.append(userAgent)
 
 
@@ -157,36 +169,39 @@ class GetHeadersBackgroundTask {
                 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none")
 
                 // Add the `Sec-Fetch-Site` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"))
+                requestHeadersBuilder.append(newLineString)
                 requestHeadersBuilder.append("Sec-Fetch-Site", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                requestHeadersBuilder.append(":  none")
+                requestHeadersBuilder.append(colonString)
+                requestHeadersBuilder.append("none")
 
 
                 // Set the `Sec-Fetch-Mode` header property.
                 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate")
 
                 // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"))
+                requestHeadersBuilder.append(newLineString)
                 requestHeadersBuilder.append("Sec-Fetch-Mode", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                requestHeadersBuilder.append(":  navigate")
+                requestHeadersBuilder.append(colonString)
+                requestHeadersBuilder.append("navigate")
 
 
                 // Set the `Sec-Fetch-User` header property.
                 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1")
 
                 // Add the `Sec-Fetch-User` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"))
+                requestHeadersBuilder.append(newLineString)
                 requestHeadersBuilder.append("Sec-Fetch-User", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                requestHeadersBuilder.append(":  ?1")
+                requestHeadersBuilder.append(colonString)
+                requestHeadersBuilder.append("?1")
 
 
                 // Set the `Accept` header property.
                 httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
 
                 // Add the `Accept` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"))
+                requestHeadersBuilder.append(newLineString)
                 requestHeadersBuilder.append("Accept", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                requestHeadersBuilder.append(":  ")
+                requestHeadersBuilder.append(colonString)
                 requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
 
 
@@ -194,9 +209,9 @@ class GetHeadersBackgroundTask {
                 httpUrlConnection.setRequestProperty("Accept-Language", localeString)
 
                 // Add the `Accept-Language` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"))
+                requestHeadersBuilder.append(newLineString)
                 requestHeadersBuilder.append("Accept-Language", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                requestHeadersBuilder.append(":  ")
+                requestHeadersBuilder.append(colonString)
                 requestHeadersBuilder.append(localeString)
 
 
@@ -209,18 +224,19 @@ class GetHeadersBackgroundTask {
                     httpUrlConnection.setRequestProperty("Cookie", cookiesString)
 
                     // Add the cookie header to the string builder and format the text.
-                    requestHeadersBuilder.append(System.getProperty("line.separator"))
+                    requestHeadersBuilder.append(newLineString)
                     requestHeadersBuilder.append("Cookie", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                    requestHeadersBuilder.append(":  ")
+                    requestHeadersBuilder.append(colonString)
                     requestHeadersBuilder.append(cookiesString)
                 }
 
 
                 // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
                 // Add the `Accept-Encoding` header to the string builder and format the text.
-                requestHeadersBuilder.append(System.getProperty("line.separator"))
+                requestHeadersBuilder.append(newLineString)
                 requestHeadersBuilder.append("Accept-Encoding", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                requestHeadersBuilder.append(":  gzip")
+                requestHeadersBuilder.append(colonString)
+                requestHeadersBuilder.append("gzip")
 
                 // Ignore SSL errors if requested.
                 if (ignoreSslErrors) {
@@ -268,9 +284,68 @@ class GetHeadersBackgroundTask {
                     // Get the response code, which causes the connection to the server to be made.
                     val responseCode = httpUrlConnection.responseCode
 
+                    // Try to populate the SSL certificate information.
+                    try {
+                        // Get the applied cipher suite string.
+                        val appliedCipherString = (httpUrlConnection as HttpsURLConnection).cipherSuite
+
+                        // Populate the applied cipher builder, returned separately.
+                        appliedCipherBuilder.append(appliedCipherString)
+
+                        // Append the applied cipher suite to the SSL information builder.
+                        sslInformationBuilder.append(application.getString(R.string.applied_cipher), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+                        sslInformationBuilder.append(colonString)
+                        sslInformationBuilder.append(appliedCipherString)
+                        sslInformationBuilder.append(newLineString)
+
+                        // Append the peer principal to the SSL information builder.
+                        sslInformationBuilder.append(application.getString(R.string.peer_principal), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+                        sslInformationBuilder.append(colonString)
+                        sslInformationBuilder.append(httpUrlConnection.peerPrincipal.toString())
+                        sslInformationBuilder.append(newLineString)
+
+                        // Get the server certificate.
+                        val serverCertificate = httpUrlConnection.serverCertificates[0]
+
+                        // Append the certificate type to the SSL information builder.
+                        sslInformationBuilder.append(application.getString(R.string.certificate_type), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+                        sslInformationBuilder.append(colonString)
+                        sslInformationBuilder.append(serverCertificate.type)
+                        sslInformationBuilder.append(newLineString)
+
+                        // Append the certificate hash code to the SSL information builder.
+                        sslInformationBuilder.append(application.getString(R.string.certificate_hash_code), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+                        sslInformationBuilder.append(colonString)
+                        sslInformationBuilder.append(serverCertificate.hashCode().toString())
+
+                        // Get the available cipher suites string array.
+                        val availableCipherSuitesStringArray = httpUrlConnection.sslSocketFactory.defaultCipherSuites
+
+                        // Get the available cipher suites string array size.
+                        val availableCipherSuitesStringArraySize = availableCipherSuitesStringArray.size
+
+                        // Populate the available cipher suites, returned separately.
+                        for (i in 0 until availableCipherSuitesStringArraySize) {
+                            // Append a new line if a cipher is already populated.
+                            if (i > 0)
+                                availableCiphersBuilder.append(newLineString)
+
+                            // Get the current cipher suite.
+                            val currentCipherSuite = availableCipherSuitesStringArray[i]
+
+                            // Append the current cipher to the list.
+                            availableCiphersBuilder.append(currentCipherSuite)
+                        }
+
+                        // Populate the SSL certificate, returned separately.
+                        sslCertificateBuilder.append(serverCertificate.toString())
+                    } catch (exception: Exception) {
+                        // Do nothing.
+                    }
+
                     // Populate the response message string builder.
                     responseMessageBuilder.append(responseCode.toString(), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                    responseMessageBuilder.append(":  ")
+                    responseMessageBuilder.append(colonString)
                     responseMessageBuilder.append(httpUrlConnection.responseMessage)
 
                     // Initialize the iteration variable.
@@ -280,11 +355,11 @@ class GetHeadersBackgroundTask {
                     while (httpUrlConnection.getHeaderField(i) != null) {
                         // Add a new line if there is already information in the string builder.
                         if (i > 0)
-                            responseHeadersBuilder.append(System.getProperty("line.separator"))
+                            responseHeadersBuilder.append(newLineString)
 
                         // Add the header to the string builder and format the text.
                         responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-                        responseHeadersBuilder.append(":  ")
+                        responseHeadersBuilder.append(colonString)
                         responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i))
 
                         // Increment the iteration variable.
@@ -331,6 +406,6 @@ class GetHeadersBackgroundTask {
         }
 
         // Return the spannable string builders.
-        return arrayOf(requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder)
+        return arrayOf(sslInformationBuilder, appliedCipherBuilder, availableCiphersBuilder, sslCertificateBuilder, requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder)
     }
 }
diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewHeadersDetailDialog.kt b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewHeadersDetailDialog.kt
new file mode 100644 (file)
index 0000000..11d2e75
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2023 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
+ *
+ * Privacy Browser Android 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 Android 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 Android.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser.dialogs
+
+import android.app.Dialog
+import android.graphics.Typeface
+import android.os.Bundle
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.style.StyleSpan
+import android.view.WindowManager
+
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import androidx.preference.PreferenceManager
+
+import com.stoutner.privacybrowser.R
+
+// Define the public class constants.
+const val AVAILABLE_CIPHERS = 0
+const val SSL_CERTIFICATE = 1
+
+// Define the private class constants.
+private const val DIALOG_TYPE = "A"
+private const val MESSAGE = "B"
+private const val APPLIED_CIPHER_STRING = "C"
+
+class ViewHeadersDetailDialog : DialogFragment() {
+    companion object {
+        fun displayDialog(dialogType: Int, message: String, appliedCipherString: String = ""): ViewHeadersDetailDialog {
+            // Create an arguments bundle.
+            val argumentsBundle = Bundle()
+
+            // Store the SSL error message components in the bundle.
+            argumentsBundle.putInt(DIALOG_TYPE, dialogType)
+            argumentsBundle.putString(MESSAGE, message)
+            argumentsBundle.putString(APPLIED_CIPHER_STRING, appliedCipherString)
+
+            // Create a new instance of the SSL certificate error dialog.
+            val thisHeadersSslCertificateDialog = ViewHeadersDetailDialog()
+
+            // Add the arguments bundle to the new dialog.
+            thisHeadersSslCertificateDialog.arguments = argumentsBundle
+
+            // Return the new dialog.
+            return thisHeadersSslCertificateDialog
+        }
+    }
+
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        // Get the arguments from the bundle.
+        val dialogType = requireArguments().getInt(DIALOG_TYPE)
+        val message = requireArguments().getString(MESSAGE)!!
+        val appliedCipherString = requireArguments().getString(APPLIED_CIPHER_STRING)!!
+
+        // Use a builder to create the alert dialog.
+        val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
+
+        // Set the icon according to the theme.
+        dialogBuilder.setIcon(R.drawable.ssl_certificate)
+
+        // Set the title and message according to the type.
+        if (dialogType == AVAILABLE_CIPHERS) {  // A cipher suite dialog is displayed.
+            // Set the title
+            dialogBuilder.setTitle(R.string.available_ciphers)
+
+            // Create a message spannable string builder with the applied cipher bolded.
+            val messageSpannableStringBuilder = SpannableStringBuilder(message)
+
+            // Get the applied cipher index.
+            val appliedCipherIndex = message.indexOf(appliedCipherString)
+
+            // Set the applied cipher to be bold.
+            messageSpannableStringBuilder.setSpan(StyleSpan(Typeface.BOLD), appliedCipherIndex, appliedCipherIndex + appliedCipherString.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+
+            // Set the message.
+            dialogBuilder.setMessage(messageSpannableStringBuilder)
+        } else {  // An SSL certificate dialog is displayed.
+            // Set the title and message.
+            dialogBuilder.setTitle(R.string.ssl_certificate)
+            dialogBuilder.setMessage(message)
+        }
+
+        // Set the close button listener.  Using `null` as the listener closes the dialog without doing anything else.
+        dialogBuilder.setNegativeButton(R.string.close, null)
+
+        // Create an alert dialog from the alert dialog builder.
+        val alertDialog = dialogBuilder.create()
+
+        // Get a handle for the shared preferences.
+        val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
+
+        // Get the screenshot preference.
+        val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
+
+        // Disable screenshots if not allowed.
+        if (!allowScreenshots) {
+            alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+        }
+
+        // Return the alert dialog.
+        return alertDialog
+    }
+}
index 83f0a36553a45c7c5145e0efce61ff58cef8fd4c..121557211d04e6d551d588bfe67a03a2294a2a91 100644 (file)
@@ -19,6 +19,7 @@
 
 package com.stoutner.privacybrowser.viewmodelfactories
 
+import android.app.Application
 import android.content.ContentResolver
 
 import androidx.lifecycle.ViewModel
@@ -27,12 +28,12 @@ import androidx.lifecycle.ViewModelProvider
 import java.net.Proxy
 import java.util.concurrent.ExecutorService
 
-class ViewHeadersFactory (private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy, private val contentResolver: ContentResolver,
-                          private val executorService: ExecutorService): ViewModelProvider.Factory {
+class ViewHeadersFactory (private val application: Application, private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy,
+                          private val contentResolver: ContentResolver, private val executorService: ExecutorService): ViewModelProvider.Factory {
     // Override the create function in order to add the provided arguments.
     override fun <T: ViewModel> create(modelClass: Class<T>): T {
         // Return a new instance of the model class with the provided arguments.
-        return modelClass.getConstructor(String::class.java, String::class.java, String::class.java, Proxy::class.java, ContentResolver::class.java, ExecutorService::class.java)
-                .newInstance(urlString, userAgent, localeString, proxy, contentResolver, executorService)
+        return modelClass.getConstructor(Application::class.java, String::class.java, String::class.java, String::class.java, Proxy::class.java, ContentResolver::class.java, ExecutorService::class.java)
+                .newInstance(application, urlString, userAgent, localeString, proxy, contentResolver, executorService)
     }
 }
index ccfe79ab2ca2baf4ee7964731ba0aad1d857eb54..3fff326322b3fcaf7b6c92210a8df9c5808756d0 100644 (file)
 
 package com.stoutner.privacybrowser.viewmodels
 
+import android.app.Application
 import android.content.ContentResolver
 import android.text.SpannableStringBuilder
 
+import androidx.lifecycle.AndroidViewModel
+
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
 
 import com.stoutner.privacybrowser.backgroundtasks.GetHeadersBackgroundTask
 
 import java.net.Proxy
 import java.util.concurrent.ExecutorService
 
-class HeadersViewModel(private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy, private val contentResolver: ContentResolver,
-                       private val executorService: ExecutorService): ViewModel() {
+class HeadersViewModel(application: Application, private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy, private val contentResolver: ContentResolver,
+                       private val executorService: ExecutorService): AndroidViewModel(application) {
     // Initialize the mutable live data variables.
     private val mutableLiveDataSourceStringArray = MutableLiveData<Array<SpannableStringBuilder>>()
     private val mutableLiveDataErrorString = MutableLiveData<String>()
@@ -43,7 +45,7 @@ class HeadersViewModel(private val urlString: String, private val userAgent: Str
         val getSourceBackgroundTask = GetHeadersBackgroundTask()
 
         // Get the headers.
-        executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, localeString, proxy, contentResolver, this,
+        executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(application, urlString, userAgent, localeString, proxy, contentResolver, this,
             false)) }
     }
 
@@ -74,7 +76,7 @@ class HeadersViewModel(private val urlString: String, private val userAgent: Str
         val getSourceBackgroundTask = GetHeadersBackgroundTask()
 
         // Get the headers.
-        executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, localeString, proxy, contentResolver, this,
+        executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(getApplication(), urlString, userAgent, localeString, proxy, contentResolver, this,
             ignoreSslErrors)) }
     }
 }
index 0e05cafde9fee52156f4652f59c5388a734d7b16..0b896c71d31b6dc1c0fd54fd3cd97955c418e6d3 100644 (file)
                     android:orientation="vertical"
                     android:layout_margin="10dp" >
 
+                    <!-- SSL information. -->
+                    <TextView
+                        android:id="@+id/ssl_information_title_textview"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:text="@string/ssl_information"
+                        android:textAlignment="center"
+                        android:textSize="18sp"
+                        android:textColor="@color/blue_text"
+                        android:textStyle="bold" />
+
+                    <TextView
+                        android:id="@+id/ssl_information_textview"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:textIsSelectable="true"
+                        android:layout_marginBottom="8dp" />
+
+                    <!-- Button row. -->
+                    <LinearLayout
+                        android:layout_height="wrap_content"
+                        android:layout_width="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="16dp"
+                        android:layout_gravity="center_horizontal" >
+
+                            <Button
+                                android:id="@+id/ciphers_button"
+                                android:layout_height="wrap_content"
+                                android:layout_width="wrap_content"
+                                android:text="@string/ciphers"
+                                android:layout_gravity="center_horizontal"
+                                android:layout_marginEnd="10dp"
+                                android:onClick="showCiphers"
+                                app:backgroundTint="@color/button_background_selector"
+                                android:textColor="@color/button_text_selector"
+                                tools:ignore="ButtonStyle" />
+
+                            <Button
+                                android:id="@+id/certificate_button"
+                                android:layout_height="wrap_content"
+                                android:layout_width="wrap_content"
+                                android:text="@string/certificate"
+                                android:onClick="showCertificate"
+                                app:backgroundTint="@color/button_background_selector"
+                                android:textColor="@color/button_text_selector"
+                                tools:ignore="ButtonStyle" />
+                    </LinearLayout>
+
                     <!-- Request headers. -->
                     <TextView
                         android:id="@+id/request_headers_title_textview"
index 3171bbcb780b2a70e601b2817fe750556d156b08..47f5a28338e6fb513155c17d439c87b7e75fb334 100644 (file)
@@ -22,6 +22,7 @@
 <androidx.coordinatorlayout.widget.CoordinatorLayout
     android:id="@+id/coordinatorlayout"
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
                     android:orientation="vertical"
                     android:layout_margin="10dp" >
 
+                    <!-- SSL information. -->
+                    <TextView
+                        android:id="@+id/ssl_information_title_textview"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:text="@string/ssl_information"
+                        android:textAlignment="center"
+                        android:textSize="18sp"
+                        android:textColor="@color/blue_text"
+                        android:textStyle="bold" />
+
+                    <TextView
+                        android:id="@+id/ssl_information_textview"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent"
+                        android:textIsSelectable="true"
+                        android:layout_marginBottom="8dp" />
+
+                    <!-- Button row. -->
+                    <LinearLayout
+                        android:layout_height="wrap_content"
+                        android:layout_width="wrap_content"
+                        android:orientation="horizontal"
+                        android:layout_marginBottom="16dp"
+                        android:layout_gravity="center_horizontal" >
+
+                        <Button
+                            android:id="@+id/ciphers_button"
+                            android:layout_height="wrap_content"
+                            android:layout_width="wrap_content"
+                            android:text="@string/ciphers"
+                            android:layout_gravity="center_horizontal"
+                            android:layout_marginEnd="10dp"
+                            android:onClick="showCiphers"
+                            app:backgroundTint="@color/button_background_selector"
+                            android:textColor="@color/button_text_selector"
+                            tools:ignore="ButtonStyle" />
+
+                        <Button
+                            android:id="@+id/certificate_button"
+                            android:layout_height="wrap_content"
+                            android:layout_width="wrap_content"
+                            android:text="@string/certificate"
+                            android:onClick="showCertificate"
+                            app:backgroundTint="@color/button_background_selector"
+                            android:textColor="@color/button_text_selector"
+                            tools:ignore="ButtonStyle" />
+                    </LinearLayout>
+
                     <!-- Request headers. -->
                     <TextView
                         android:id="@+id/request_headers_title_textview"
index 32dfe74616c858efe09f5200fe145b9da5843df7..4cc9670c21b7f3de8e68ca5c0d62a924ee928d13 100644 (file)
     <string name="error_saving_file">Fehler beim Speichern der Datei %1$s:\u0020 %2$s</string>
     <string name="unknown_error">Unbekannter Fehler</string>
 
-    <!-- View Headers. -->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <string name="colon">: \u0020</string>
     <string name="request_headers">Anfragekopfzeilen</string>
     <string name="response_message">Status-Code</string>
     <string name="response_headers">Antwortkopfzeilen</string>
index 4fec6f3834bbac707f1a6e410bed69a683a8df0c..5c0a40b11545e66d3c5474cc1ac3888ae4bc36a1 100644 (file)
     <string name="error_saving_file">Error al guardar %1$s:\u0020 %2$s</string>
     <string name="unknown_error">Error desconocido</string>
 
-    <!-- View Headers. -->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <string name="colon">: \u0020</string>
     <string name="request_headers">Cabeceras de solicitud</string>
     <string name="response_message">Mensaje de respuesta</string>
     <string name="response_headers">Cabeceras de respuesta</string>
index 0bc9d414fb614cd45f385c59bb922f508181e249..795847187a78dcd123d748697d82387545a05f70 100644 (file)
     <string name="error_saving_file">Erreur lors de l\'enregistrement de %1$s : %2$s</string>
     <string name="unknown_error">Erreur inconnue</string>
 
-    <!-- View Headers. -->
+    <!-- View Headers. Android removes initial and trailing spaces, but they can be manually specified with the Unicode `\u0020` formatting.-->
+    <string name="colon">\u0020:\u0020</string>
     <string name="request_headers">En-tête de la requête</string>
     <string name="response_message">Message de la réponse</string>
     <string name="response_headers">En-tête de la réponse</string>
index 8f3a616dd8ba90aac71f1c24cdbaa785644d8fae..6c802033c8a7e2a1ef4430916ebe673da33ae75e 100644 (file)
     <string name="error_saving_file">Erro ao salvar %1$s:\u0020 %2$s</string>
     <string name="unknown_error">Erro desconhecido</string>
 
-    <!-- View Headers. -->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <string name="colon">: \u0020</string>
     <string name="request_headers">Solicitar cabeçalhos</string>
     <string name="response_message">Mensagem de Resposta</string>
     <string name="response_headers">Cabeçalhos de resposta</string>
index 48c498da0b668b37fa87c552d66a562160bd6b50..0a8ea2b461a15dd039b7a4dcbbaad147a165d660 100644 (file)
     <string name="error_saving_file">Ошибка сохранения %1$s:\u0020 %2$s</string>
     <string name="unknown_error">Неизвестная ошибка</string>
 
-    <!-- View Headers. -->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <string name="colon">: \u0020</string>
     <string name="request_headers">Заголовки запроса</string>
     <string name="response_message">Ответное сообщение</string>
     <string name="response_headers">Заголовки ответа</string>
index cd078e1c230826279cbcdc97178539828676b782..63cf8548651082d61a452be0bc106e72437b4d8a 100644 (file)
     <string name="file_name">Dosya adı</string>
     <string name="unknown_size">Bilinmeyen boyut</string>
 
-    <!-- View Headers. -->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <string name="colon">: \u0020</string>
     <string name="request_headers">İstek Başlıkları</string>
     <string name="response_message">Yanıt Mesajı</string>
     <string name="response_headers">Yanıt Başlıkları</string>
index c992c9583f7cc2bc7006754f8861867dfc88b36d..37f2e2bfef4af9a03a6daf05b126318102c60be4 100644 (file)
     <string name="error_saving_file">保存失败 %1$s:\u0020 %2$s</string>
     <string name="unknown_error">未知错误</string>
 
-    <!-- View Headers. -->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <string name="colon">: \u0020</string>
     <string name="request_headers">请求头</string>
     <string name="response_message">响应</string>
     <string name="response_headers">响应头</string>
index 56bb63f1c209c86134273f426f90a3e2bf4b9c81..2183fe048c1299536be06e0aad82a3a0863a2482 100644 (file)
     <string name="error_saving_file">Error saving %1$s:\u0020 %2$s</string>
     <string name="unknown_error">Unknown error</string>
 
-    <!-- View Headers. -->
+    <!-- View Headers. Android removes double spaces, but extra spaces can be manually specified with the Unicode `\u0020` formatting.-->
+    <string name="colon">: \u0020</string>
+    <string name="ssl_information">SSL Information</string>
+    <string name="applied_cipher">Applied Cipher</string>
+    <string name="peer_principal">Peer Principal</string>
+    <string name="certificate_type">Certificate Type</string>
+    <string name="certificate_hash_code">Certificate Hash Code</string>
+    <string name="ciphers">Ciphers</string>
+    <string name="available_ciphers">Available Ciphers</string>
+    <string name="certificate">Certificate</string>
     <string name="request_headers">Request Headers</string>
     <string name="response_message">Response Message</string>
     <string name="response_headers">Response Headers</string>