Migrate the rest of the dialogs to Kotlin. https://redmine.stoutner.com/issues/683
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / dialogs / ViewSslCertificateDialog.kt
1 /*
2  * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.dialogs
21
22 import android.annotation.SuppressLint
23 import android.app.Dialog
24 import android.content.res.Configuration
25 import android.graphics.drawable.BitmapDrawable
26 import android.graphics.drawable.Drawable
27 import android.net.Uri
28 import android.os.Bundle
29 import android.text.SpannableStringBuilder
30 import android.text.Spanned
31 import android.text.style.ForegroundColorSpan
32 import android.view.WindowManager
33 import android.widget.TextView
34
35 import androidx.appcompat.app.AlertDialog
36 import androidx.fragment.app.DialogFragment
37 import androidx.preference.PreferenceManager
38
39 import com.stoutner.privacybrowser.R
40 import com.stoutner.privacybrowser.activities.MainWebViewActivity
41 import com.stoutner.privacybrowser.views.NestedScrollWebView
42
43 import java.text.DateFormat
44 import java.util.Calendar
45
46 // Define the class constants.
47 private const val WEBVIEW_FRAGMENT_ID = "webview_fragment_id"
48
49 class ViewSslCertificateDialog : DialogFragment() {
50     companion object {
51         // `@JvmStatic` will no longer be required once all the code has transitioned to Kotlin.
52         @JvmStatic
53         fun displayDialog(webViewFragmentId: Long): ViewSslCertificateDialog {
54             // Create an arguments bundle.
55             val argumentsBundle = Bundle()
56
57             // Store the WebView fragment ID in the bundle.
58             argumentsBundle.putLong(WEBVIEW_FRAGMENT_ID, webViewFragmentId)
59
60             // Create a new instance of the view SSL certificate dialog.
61             val viewSslCertificateDialog = ViewSslCertificateDialog()
62
63             // Add the bundle to the new dialog.
64             viewSslCertificateDialog.arguments = argumentsBundle
65
66             // Return the new dialog.
67             return viewSslCertificateDialog
68         }
69     }
70
71     // `@SuppressLint("InflateParams")` removes the warning about using `null` as the parent view group when inflating the alert dialog.
72     @SuppressLint("InflateParams")
73     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
74         // Get the current position of this WebView fragment.
75         val webViewPosition = MainWebViewActivity.webViewPagerAdapter.getPositionForId(requireArguments().getLong(WEBVIEW_FRAGMENT_ID))
76
77         // Get the WebView tab fragment.
78         val webViewTabFragment = MainWebViewActivity.webViewPagerAdapter.getPageFragment(webViewPosition)
79
80         // Get the fragment view.
81         val fragmentView = webViewTabFragment.requireView()
82
83         // Get a handle for the current nested scroll WebView.
84         val nestedScrollWebView: NestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview)
85
86         // Use a builder to create the alert dialog.
87         val dialogBuilder = AlertDialog.Builder(requireContext(), R.style.PrivacyBrowserAlertDialog)
88
89         // Create a drawable version of the favorite icon.
90         val favoriteIconDrawable: Drawable = BitmapDrawable(resources, nestedScrollWebView.favoriteOrDefaultIcon)
91
92         // Set the icon.
93         dialogBuilder.setIcon(favoriteIconDrawable)
94
95         // Set the close button listener.  Using `null` as the listener closes the dialog without doing anything else.
96         dialogBuilder.setNegativeButton(R.string.close, null)
97
98         // Get the SSL certificate.
99         val sslCertificate = nestedScrollWebView.certificate
100
101         // Get a handle for the shared preferences.
102         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
103
104         // Get the screenshot preference.
105         val allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false)
106
107         // Check to see if the website is encrypted.
108         if (sslCertificate == null) {  // The website is not encrypted.
109             // Set the title.
110             dialogBuilder.setTitle(R.string.unencrypted_website)
111
112             // Set the Layout.  The parent view is `null` because it will be assigned by the alert dialog.
113             dialogBuilder.setView(layoutInflater.inflate(R.layout.unencrypted_website_dialog, null))
114
115             // Create an alert dialog from the builder.
116             val alertDialog = dialogBuilder.create()
117
118             // Disable screenshots if not allowed.
119             if (!allowScreenshots) {
120                 // Disable screenshots.
121                 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
122             }
123
124             // Return the alert dialog.
125             return alertDialog
126         } else {  // The website is encrypted.
127             // Set the title.
128             dialogBuilder.setTitle(R.string.ssl_certificate)
129
130             // Set the layout.  The parent view is `null` because it will be assigned by the alert dialog.
131             dialogBuilder.setView(layoutInflater.inflate(R.layout.view_ssl_certificate_dialog, null))
132
133             // Create an alert dialog from the builder.
134             val alertDialog = dialogBuilder.create()
135
136             // Disable screenshots if not allowed.
137             if (!allowScreenshots) {
138                 // Disable screenshots.
139                 alertDialog.window!!.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
140             }
141
142             // The alert dialog must be shown before items in the layout can be modified.
143             alertDialog.show()
144
145             // Get handles for the text views.
146             val domainTextView = alertDialog.findViewById<TextView>(R.id.domain)!!
147             val ipAddressesTextView = alertDialog.findViewById<TextView>(R.id.ip_addresses)!!
148             val issuedToCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_cname)!!
149             val issuedToONameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_oname)!!
150             val issuedToUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_to_uname)!!
151             val issuedByCNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_cname)!!
152             val issuedByONameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_oname)!!
153             val issuedByUNameTextView = alertDialog.findViewById<TextView>(R.id.issued_by_uname)!!
154             val startDateTextView = alertDialog.findViewById<TextView>(R.id.start_date)!!
155             val endDateTextView = alertDialog.findViewById<TextView>(R.id.end_date)!!
156
157             // Setup the labels.
158             val domainLabel = getString(R.string.domain_label) + "  "
159             val ipAddressesLabel = getString(R.string.ip_addresses) + "  "
160             val cNameLabel = getString(R.string.common_name) + "  "
161             val oNameLabel = getString(R.string.organization) + "  "
162             val uNameLabel = getString(R.string.organizational_unit) + "  "
163             val startDateLabel = getString(R.string.start_date) + "  "
164             val endDateLabel = getString(R.string.end_date) + "  "
165
166             // Convert the URL to a URI.
167             val uri = Uri.parse(nestedScrollWebView.url)
168
169             // Extract the domain name from the URI.
170             val domainString = uri.host
171
172             // Get the strings from the SSL certificate.
173             val issuedToCName = sslCertificate.issuedTo.cName
174             val issuedToOName = sslCertificate.issuedTo.oName
175             val issuedToUName = sslCertificate.issuedTo.uName
176             val issuedByCName = sslCertificate.issuedBy.cName
177             val issuedByOName = sslCertificate.issuedBy.oName
178             val issuedByUName = sslCertificate.issuedBy.uName
179             val startDate = sslCertificate.validNotBeforeDate
180             val endDate = sslCertificate.validNotAfterDate
181
182             // Create spannable string builders for each text view that needs multiple colors of text.
183             val domainStringBuilder = SpannableStringBuilder(domainLabel + domainString)
184             val ipAddressesStringBuilder = SpannableStringBuilder(ipAddressesLabel + nestedScrollWebView.currentIpAddresses)
185             val issuedToCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedToCName)
186             val issuedToONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedToOName)
187             val issuedToUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedToUName)
188             val issuedByCNameStringBuilder = SpannableStringBuilder(cNameLabel + issuedByCName)
189             val issuedByONameStringBuilder = SpannableStringBuilder(oNameLabel + issuedByOName)
190             val issuedByUNameStringBuilder = SpannableStringBuilder(uNameLabel + issuedByUName)
191             val startDateStringBuilder = SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate))
192             val endDateStringBuilder = SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate))
193
194             // Define the color spans.
195             val blueColorSpan: ForegroundColorSpan
196             val redColorSpan: ForegroundColorSpan
197
198             // Get the current theme status.
199             val currentThemeStatus = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
200
201             // Set the color spans according to the theme.  The deprecated `getColor()` must be used until the minimum API >= 23.
202             if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
203                 @Suppress("DEPRECATION")
204                 blueColorSpan = ForegroundColorSpan(resources.getColor(R.color.blue_700))
205                 @Suppress("DEPRECATION")
206                 redColorSpan = ForegroundColorSpan(resources.getColor(R.color.red_a700))
207             } else {
208                 @Suppress("DEPRECATION")
209                 blueColorSpan = ForegroundColorSpan(resources.getColor(R.color.violet_700))
210                 @Suppress("DEPRECATION")
211                 redColorSpan = ForegroundColorSpan(resources.getColor(R.color.red_900))
212             }
213
214             // Format the domain string and issued to CName colors.
215             if (domainString == issuedToCName) {  // The domain and issued to CName match.
216                 // Set the strings to be blue.
217                 domainStringBuilder.setSpan(blueColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
218                 issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
219             } else if (issuedToCName.startsWith("*.")) {  // The issued to CName begins with a wildcard.
220                 // Remove the initial `*.`.
221                 val baseCertificateDomain = issuedToCName.substring(2)
222
223                 // Setup a copy of the domain string to test subdomains.
224                 var domainStringSubdomain = domainString!!
225
226                 // Define a domain names match variable.
227                 var domainNamesMatch = false
228
229                 // Check all the subdomains against the base certificate domain.
230                 while (!domainNamesMatch && domainStringSubdomain.contains(".")) {  // Stop checking if we know that the domain names match or if we run out of subdomains.
231                     // Test the subdomain against the base certificate domain.
232                     if (domainStringSubdomain == baseCertificateDomain) {
233                         domainNamesMatch = true
234                     }
235
236                     // Strip out the lowest subdomain.
237                     domainStringSubdomain = domainStringSubdomain.substring(domainStringSubdomain.indexOf(".") + 1)
238                 }
239
240                 // Format the domain and issued to CName.
241                 if (domainNamesMatch) {  // The domain is a subdomain of the wildcard certificate.
242                     // Set the strings to be blue.
243                     domainStringBuilder.setSpan(blueColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
244                     issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
245                 } else {  // The domain is not a subdomain of the wildcard certificate.
246                     // Set the string to be red.
247                     domainStringBuilder.setSpan(redColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
248                     issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
249                 }
250             } else {  // The strings do not match and issued to CName does not begin with a wildcard.
251                 // Set the strings to be red.
252                 domainStringBuilder.setSpan(redColorSpan, domainLabel.length, domainStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
253                 issuedToCNameStringBuilder.setSpan(redColorSpan, cNameLabel.length, issuedToCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
254             }
255
256             // Set the IP addresses, issued to, and issued by spans to display the certificate information in blue.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
257             ipAddressesStringBuilder.setSpan(blueColorSpan, ipAddressesLabel.length, ipAddressesStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
258             issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedToONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
259             issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedToUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
260             issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length, issuedByCNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
261             issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length, issuedByONameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
262             issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length, issuedByUNameStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
263
264             // Get the current date.
265             val currentDate = Calendar.getInstance().time
266
267             //  Format the start date color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
268             if (startDate.after(currentDate)) {  // The certificate start date is in the future.
269                 startDateStringBuilder.setSpan(redColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
270             } else {  // The certificate start date is in the past.
271                 startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length, startDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
272             }
273
274             // Format the end date color.  `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction.
275             if (endDate.before(currentDate)) {  // The certificate end date is in the past.
276                 endDateStringBuilder.setSpan(redColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
277             } else {  // The certificate end date is in the future.
278                 endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length, endDateStringBuilder.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
279             }
280
281             // Display the strings.
282             domainTextView.text = domainStringBuilder
283             ipAddressesTextView.text = ipAddressesStringBuilder
284             issuedToCNameTextView.text = issuedToCNameStringBuilder
285             issuedToONameTextView.text = issuedToONameStringBuilder
286             issuedToUNameTextView.text = issuedToUNameStringBuilder
287             issuedByCNameTextView.text = issuedByCNameStringBuilder
288             issuedByONameTextView.text = issuedByONameStringBuilder
289             issuedByUNameTextView.text = issuedByUNameStringBuilder
290             startDateTextView.text = startDateStringBuilder
291             endDateTextView.text = endDateStringBuilder
292
293             // Return the alert dialog.
294             return alertDialog
295         }
296     }
297 }