X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.java;h=65528ebf25d8b6cf222df1599154254e0f10d732;hp=96098947696d915603fa7e53d69d4b4de556a812;hb=be2cf6a2f7e8fbf094c0d7e7733b5ea52ada32d0;hpb=9c9159715c89e30aec11ae7e7a1d7591006e5cbe diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 96098947..65528ebf 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -47,6 +47,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.net.http.SslCertificate; import android.net.http.SslError; +import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Environment; @@ -60,6 +61,7 @@ import android.support.design.widget.FloatingActionButton; import android.support.design.widget.NavigationView; import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentManager; import android.support.v4.content.ContextCompat; // `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat version once API >= 26. import android.support.v4.content.pm.ShortcutInfoCompat; @@ -123,7 +125,7 @@ import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog; import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog; import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog; import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog; -import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog; +import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog; import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog; import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog; import com.stoutner.privacybrowser.helpers.AdHelper; @@ -139,10 +141,13 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.lang.ref.WeakReference; +import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -155,7 +160,7 @@ import java.util.Set; public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, - HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, + HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedMismatchDialog.PinnedMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener { // `darkTheme` is public static so it can be accessed from everywhere. @@ -169,14 +174,21 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`. public static Bitmap favoriteIconBitmap; - // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`. + // `favoriteIconDefaultBitmap` public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()` and `applyDomainSettings`. + public static Bitmap favoriteIconDefaultBitmap; + + // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, `CreateBookmarkDialog`, and `PinnedMismatchDialog`. // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`. public static String formattedUrlString; - // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`, - // and `ViewSslCertificateDialog`. It is also used in `onCreate()`. + // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedMismatchDialog`, and `ViewSslCertificateDialog`. + // It is also used in `onCreate()` and `checkPinnedMismatch()`. public static SslCertificate sslCertificate; + // `currentHostIpAddresses` is public static so it can be accessed from `DomainSettingsFragment` and `ViewSslCertificateDialog`. + // It is also used in `onCreate()` and `GetHostIpAddresses()`. + public static String currentHostIpAddresses; + // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`. public static String orbotStatus; @@ -257,18 +269,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`. public static String currentBookmarksFolder; - // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedSslCertificateMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. + // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`. public static int domainSettingsDatabaseId; - // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`. They are also used in `onCreate()` and `applyDomainSettings()`. - public static String pinnedDomainSslIssuedToCNameString; - public static String pinnedDomainSslIssuedToONameString; - public static String pinnedDomainSslIssuedToUNameString; - public static String pinnedDomainSslIssuedByCNameString; - public static String pinnedDomainSslIssuedByONameString; - public static String pinnedDomainSslIssuedByUNameString; - public static Date pinnedDomainSslStartDate; - public static Date pinnedDomainSslEndDate; + // The pinned variables are public static so they can be accessed from `PinnedMismatchDialog`. They are also used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`. + public static String pinnedSslIssuedToCName; + public static String pinnedSslIssuedToOName; + public static String pinnedSslIssuedToUName; + public static String pinnedSslIssuedByCName; + public static String pinnedSslIssuedByOName; + public static String pinnedSslIssuedByUName; + public static Date pinnedSslStartDate; + public static Date pinnedSslEndDate; + public static String pinnedHostIpAddresses; // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`. public final static int UNRECOGNIZED_USER_AGENT = -1; @@ -279,15 +292,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public final static int DOMAINS_CUSTOM_USER_AGENT = 13; + + // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, `applyDomainSettings()`, and `GetHostIpAddresses`. + private static boolean urlIsLoading; + + // `gettingIpAddresses` is used in `onCreate() and `GetHostIpAddresses`. + private static boolean gettingIpAddresses; + + // `pinnedDomainSslCertificate` is used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`. + private static boolean pinnedSslCertificate; + + // `pinnedIpAddress` is used in `applyDomainSettings()` and `checkPinnedMismatch()`. + private static boolean pinnedIpAddresses; + + // `ignorePinnedDomainInformation` is used in `onSslMismatchProceed()`, `applyDomainSettings()`, and `checkPinnedMismatch()`. + private static boolean ignorePinnedDomainInformation; + + // `supportFragmentManager` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateContextMenu()`, `onRequestPermissionResult()`, `viewSslCertificate()`, + // `applyAppSettings()`, and `checkPinnedMismatch()`. + private static FragmentManager supportFragmentManager; + + // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`. private ActionBar appBar; // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`. private boolean navigatingHistory; - // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`. - private Bitmap favoriteIconDefaultBitmap; - // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`. private DrawerLayout drawerLayout; @@ -404,9 +435,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`. private String currentDomainName; - // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`. - private boolean ignorePinnedSslCertificate; - // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`. private BroadcastReceiver orbotStatusBroadcastReceiver; @@ -465,12 +493,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`. private RelativeLayout mainWebViewRelativeLayout; - // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, and `applyDomainSettings()`. - private boolean urlIsLoading; - - // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`. - private boolean pinnedDomainSslCertificate; - // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, // and `loadBookmarksFolder()`. private BookmarksDatabaseHelper bookmarksDatabaseHelper; @@ -541,8 +563,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the content view. setContentView(R.layout.main_drawerlayout); - // Get a handle for the resources. + // Get a handle for the resources and the support fragment manager. Resources resources = getResources(); + supportFragmentManager = getSupportFragmentManager(); // Get a handle for `inputMethodManager`. inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); @@ -672,14 +695,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook createBookmarkFolderFab.setOnClickListener(v -> { // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`. AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog(); - createBookmarkFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_folder)); + createBookmarkFolderDialog.show(supportFragmentManager, resources.getString(R.string.create_folder)); }); // Set the create new bookmark FAB to display an alert dialog. createBookmarkFab.setOnClickListener(view -> { // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`. AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog(); - createBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_bookmark)); + createBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.create_bookmark)); }); // Create a double-tap listener to toggle full-screen mode. @@ -899,11 +922,11 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`. AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId); - editFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_folder)); + editFolderDialog.show(supportFragmentManager, resources.getString(R.string.edit_folder)); } else { // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`. AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId); - editBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_bookmark)); + editBookmarkDialog.show(supportFragmentManager, resources.getString(R.string.edit_bookmark)); } // Consume the event. @@ -1211,7 +1234,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength); // Show the download file alert dialog. - downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + downloadFileDialogFragment.show(supportFragmentManager, getString(R.string.download)); } } }); @@ -1589,12 +1612,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display the HTTP authentication dialog. AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm); - httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication)); + httpAuthenticationDialogFragment.show(supportFragmentManager, getString(R.string.http_authentication)); } // Update the URL in urlTextBox when the page starts to load. @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { + // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page. + // This is also used to determine when to check for pinned mismatches. + urlIsLoading = true; + + // Reset the list of host IP addresses. + currentHostIpAddresses = ""; + // Reset the list of resource requests. resourceRequests.clear(); @@ -1616,7 +1646,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); // Check to see if Privacy Browser is waiting on Orbot. - if (!waitingForOrbot) { // We are not waiting on Orbot, so we need to process the URL. + if (!waitingForOrbot) { // Process the URL. // The formatted URL string must be updated at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded. formattedUrlString = url; @@ -1626,6 +1656,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Apply text highlighting to `urlTextBox`. highlightUrlText(); + // Get a URI for the current URL. + Uri currentUri = Uri.parse(formattedUrlString); + + // Get the IP addresses for the host. + new GetHostIpAddresses(activity).execute(currentUri.getHost()); + // Apply any custom domain settings if the URL was loaded by navigating history. if (navigatingHistory) { // Apply the domain settings. @@ -1640,9 +1676,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page. - urlIsLoading = true; - // Replace Refresh with Stop if the menu item has been created. (The WebView typically begins loading before the menu items are instantiated.) if (refreshMenuItem != null) { // Set the title. @@ -1689,8 +1722,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes. - urlIsLoading = false; + // Clear the cache and history if Incognito Mode is enabled. if (incognitoModeEnabled) { @@ -1713,7 +1745,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Update `urlTextBox` and apply domain settings if not waiting on Orbot. + // Update the URL text box and apply domain settings if not waiting on Orbot. if (!waitingForOrbot) { // Check to see if `WebView` has set `url` to be `about:blank`. if (url.equals("about:blank")) { // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint. @@ -1744,70 +1776,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } } - // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedSslCertificateMismatchDialog`. + // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedMismatchDialog`. sslCertificate = mainWebView.getCertificate(); - // Check the current website SSL certificate against the pinned SSL certificate if there is a pinned SSL certificate the user has not chosen to ignore it for this session. - // Also ignore if changes in the user agent causes an error while navigating history. - if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate && navigatingHistory) { - // Initialize the current SSL certificate variables. - String currentWebsiteIssuedToCName = ""; - String currentWebsiteIssuedToOName = ""; - String currentWebsiteIssuedToUName = ""; - String currentWebsiteIssuedByCName = ""; - String currentWebsiteIssuedByOName = ""; - String currentWebsiteIssuedByUName = ""; - Date currentWebsiteSslStartDate = null; - Date currentWebsiteSslEndDate = null; - - - // Extract the individual pieces of information from the current website SSL certificate if it is not null. - if (sslCertificate != null) { - currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName(); - currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName(); - currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName(); - currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName(); - currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName(); - currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName(); - currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate(); - currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate(); - } - - // Initialize `String` variables to store the SSL certificate dates. `Strings` are needed to compare the values below, which doesn't work with `Dates` if they are `null`. - String currentWebsiteSslStartDateString = ""; - String currentWebsiteSslEndDateString = ""; - String pinnedDomainSslStartDateString = ""; - String pinnedDomainSslEndDateString = ""; - - // Convert the `Dates` to `Strings` if they are not `null`. - if (currentWebsiteSslStartDate != null) { - currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString(); - } - - if (currentWebsiteSslEndDate != null) { - currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString(); - } - - if (pinnedDomainSslStartDate != null) { - pinnedDomainSslStartDateString = pinnedDomainSslStartDate.toString(); - } - - if (pinnedDomainSslEndDate != null) { - pinnedDomainSslEndDateString = pinnedDomainSslEndDate.toString(); - } - - // Check to see if the pinned SSL certificate matches the current website certificate. - if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) || - !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) || !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) || - !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) || - !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) { - // The pinned SSL certificate doesn't match the current domain certificate. - //Display the pinned SSL certificate mismatch `AlertDialog`. - AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog(); - pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch)); - } + // Check the current website information against any pinned domain information if the current IP addresses have been loaded. + if (!gettingIpAddresses) { + checkPinnedMismatch(); } } + + // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes. It is also used to determine when to check for pinned mismatches. + urlIsLoading = false; } // Handle SSL Certificate errors. @@ -1827,13 +1806,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate(); // Proceed to the website if the current SSL website certificate matches the pinned domain certificate. - if (pinnedDomainSslCertificate && - currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) && currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) && - currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) && currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) && - currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) && - currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) { - // An SSL certificate is pinned and matches the current domain certificate. - // Proceed to the website without displaying an error. + if (pinnedSslCertificate && + currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) && currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) && + currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) && currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) && + currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) && currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) && + currentWebsiteSslStartDate.equals(pinnedSslStartDate) && currentWebsiteSslEndDate.equals(pinnedSslEndDate)) { + + // An SSL certificate is pinned and matches the current domain certificate. Proceed to the website without displaying an error. handler.proceed(); } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate. // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`. @@ -1841,7 +1820,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display the SSL error `AlertDialog`. AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error); - sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error)); + sslCertificateErrorDialogFragment.show(supportFragmentManager, getString(R.string.ssl_certificate_error)); } } }); @@ -2849,20 +2828,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook mainWebView.reload(); return true; - case R.id.print: - // Get a `PrintManager` instance. - PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); - - // Convert `mainWebView` to `printDocumentAdapter`. - PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter(); - - // Remove the lint error below that `printManager` might be `null`. - assert printManager != null; - - // Print the document. The print attributes are `null`. - printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); - return true; - case R.id.find_on_page: // Hide the URL app bar. supportAppBar.setVisibility(View.GONE); @@ -2881,14 +2846,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook }, 200); return true; - case R.id.add_to_homescreen: - // Show the alert dialog. - AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog(); - createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut)); - - //Everything else will be handled by the alert dialog and the associated listener below. - return true; - case R.id.view_source: // Launch the View Source activity. Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class); @@ -2908,32 +2865,34 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url))); return true; - case R.id.open_with_app: - // Create the open with intent with `ACTION_VIEW`. - Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW); + case R.id.print: + // Get a `PrintManager` instance. + PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); + + // Convert `mainWebView` to `printDocumentAdapter`. + PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter(); - // Set the URI but not the MIME type. This should open all available apps. - openWithAppIntent.setData(Uri.parse(formattedUrlString)); + // Remove the lint error below that `printManager` might be `null`. + assert printManager != null; - // Flag the intent to open in a new task. - openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // Print the document. The print attributes are `null`. + printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); + return true; - // Show the chooser. - startActivity(Intent.createChooser(openWithAppIntent, getString(R.string.open_with))); + case R.id.open_with_app: + openWithApp(formattedUrlString); return true; case R.id.open_with_browser: - // Create the open with intent with `ACTION_VIEW`. - Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW); - - // Set the URI and the MIME type. `"text/html"` should load browser options. - openWithBrowserIntent.setDataAndType(Uri.parse(formattedUrlString), "text/html"); + openWithBrowser(formattedUrlString); + return true; - // Flag the intent to open in a new task. - openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + case R.id.add_to_homescreen: + // Show the alert dialog. + AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog(); + createHomeScreenShortcutDialogFragment.show(supportFragmentManager, getString(R.string.create_shortcut)); - // Show the chooser. - startActivity(Intent.createChooser(openWithBrowserIntent, getString(R.string.open_with))); + //Everything else will be handled by the alert dialog and the associated listener below. return true; case R.id.proxy_through_orbot: @@ -3009,7 +2968,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`. `this` is the `Context`. AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList); - urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history)); + urlHistoryDialogFragment.show(supportFragmentManager, getString(R.string.history)); break; case R.id.requests: @@ -3301,13 +3260,25 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1); // Show the download file alert dialog. - downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + downloadFileDialogFragment.show(supportFragmentManager, getString(R.string.download)); } } return false; }); - // Add a `Cancel` entry, which by default closes the `ContextMenu`. + // Add an Open with App entry. + menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { + openWithApp(linkUrl); + return false; + }); + + // Add an Open with Browser entry. + menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> { + openWithBrowser(linkUrl); + return false; + }); + + // Add a Cancel entry, which by default closes the context menu. menu.add(R.string.cancel); break; @@ -3318,7 +3289,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the target URL as the title of the `ContextMenu`. menu.setHeaderTitle(linkUrl); - // Add a `Write Email` entry. + // Add a Write Email entry. menu.add(R.string.write_email).setOnMenuItemClickListener(item -> { // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. Intent emailIntent = new Intent(Intent.ACTION_SENDTO); @@ -3334,7 +3305,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return false; }); - // Add a `Copy Email Address` entry. + // Add a Copy Email Address entry. menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> { // Save the email address in a `ClipData`. ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl); @@ -3356,13 +3327,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the image URL as the title of the `ContextMenu`. menu.setHeaderTitle(imageUrl); - // Add a `View Image` entry. + // Add a View Image entry. menu.add(R.string.view_image).setOnMenuItemClickListener(item -> { loadUrl(imageUrl); return false; }); - // Add a `Download Image` entry. + // Add a Download Image entry. menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> { // Check if the download should be processed by an external app. if (downloadWithExternalApp) { // Download with an external app. @@ -3389,13 +3360,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); // Show the download image alert dialog. - downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + downloadImageDialogFragment.show(supportFragmentManager, getString(R.string.download)); } } return false; }); - // Add a `Copy URL` entry. + // Add a Copy URL entry. menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> { // Save the image URL in a `ClipData`. ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl); @@ -3405,6 +3376,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return false; }); + // Add an Open with App entry. + menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { + openWithApp(imageUrl); + return false; + }); + + // Add an Open with Browser entry. + menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> { + openWithBrowser(imageUrl); + return false; + }); + // Add a `Cancel` entry, which by default closes the `ContextMenu`. menu.add(R.string.cancel); break; @@ -3451,7 +3434,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); // Show the download image alert dialog. - downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + downloadImageDialogFragment.show(supportFragmentManager, getString(R.string.download)); } } return false; @@ -3467,6 +3450,18 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return false; }); + // Add an Open with App entry. + menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> { + openWithApp(imageUrl); + return false; + }); + + // Add an Open with Browser entry. + menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> { + openWithBrowser(imageUrl); + return false; + }); + // Add a `Cancel` entry, which by default closes the `ContextMenu`. menu.add(R.string.cancel); break; @@ -3598,9 +3593,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // On API 23, displaying the fragment must be delayed or the app will crash. if (Build.VERSION.SDK_INT == 23) { - new Handler().postDelayed(() -> downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500); + new Handler().postDelayed(() -> downloadFileDialogFragment.show(supportFragmentManager, getString(R.string.download)), 500); } else { - downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + downloadFileDialogFragment.show(supportFragmentManager, getString(R.string.download)); } // Reset the download variables. @@ -3615,9 +3610,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // On API 23, displaying the fragment must be delayed or the app will crash. if (Build.VERSION.SDK_INT == 23) { - new Handler().postDelayed(() -> downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500); + new Handler().postDelayed(() -> downloadImageDialogFragment.show(supportFragmentManager, getString(R.string.download)), 500); } else { - downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)); + downloadImageDialogFragment.show(supportFragmentManager, getString(R.string.download)); } // Reset the image URL variable. @@ -3856,7 +3851,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onSslMismatchBack() { + public void onPinnedMismatchBack() { if (mainWebView.canGoBack()) { // There is a back page in the history. // Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled. formattedUrlString = ""; @@ -3873,9 +3868,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onSslMismatchProceed() { - // Do not check the pinned SSL certificate for this domain again until the domain changes. - ignorePinnedSslCertificate = true; + public void onPinnedMismatchProceed() { + // Do not check the pinned information for this domain again until the domain changes. + ignorePinnedDomainInformation = true; } @Override @@ -4170,8 +4165,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the new `hostname` as the `currentDomainName`. currentDomainName = hostName; - // Reset `ignorePinnedSslCertificate`. - ignorePinnedSslCertificate = false; + // Reset the ignoring of pinned domain information. + ignorePinnedDomainInformation = false; // Reset the favorite icon if specified. if (resetFavoriteIcon) { @@ -4263,13 +4258,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH)); int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE)); int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES)); - pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1); - pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); - pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); - pinnedDomainSslIssuedToUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)); - pinnedDomainSslIssuedByCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)); - pinnedDomainSslIssuedByONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)); - pinnedDomainSslIssuedByUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)); + pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1); + pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME)); + pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION)); + pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT)); + pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME)); + pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION)); + pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT)); + pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1); + pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES)); // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used. switch (nightModeInt) { @@ -4292,16 +4289,16 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0. if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) { - pinnedDomainSslStartDate = null; + pinnedSslStartDate = null; } else { - pinnedDomainSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE))); + pinnedSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE))); } // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0. if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) { - pinnedDomainSslEndDate = null; + pinnedSslEndDate = null; } else { - pinnedDomainSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE))); + pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE))); } // Close `currentHostDomainSettingsCursor`. @@ -4454,17 +4451,19 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); } - // Reset the pinned SSL certificate information. + // Reset the pinned variables. domainSettingsDatabaseId = -1; - pinnedDomainSslCertificate = false; - pinnedDomainSslIssuedToCNameString = ""; - pinnedDomainSslIssuedToONameString = ""; - pinnedDomainSslIssuedToUNameString = ""; - pinnedDomainSslIssuedByCNameString = ""; - pinnedDomainSslIssuedByONameString = ""; - pinnedDomainSslIssuedByUNameString = ""; - pinnedDomainSslStartDate = null; - pinnedDomainSslEndDate = null; + pinnedSslCertificate = false; + pinnedSslIssuedToCName = ""; + pinnedSslIssuedToOName = ""; + pinnedSslIssuedToUName = ""; + pinnedSslIssuedByCName = ""; + pinnedSslIssuedByOName = ""; + pinnedSslIssuedByUName = ""; + pinnedSslStartDate = null; + pinnedSslEndDate = null; + pinnedIpAddresses = false; + pinnedHostIpAddresses = ""; // Set third-party cookies status if API >= 21. if (Build.VERSION.SDK_INT >= 21) { @@ -4509,7 +4508,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the loading of webpage images. mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); - // Set a transparent background on `urlTextBox`. We have to use the deprecated `.getDrawable()` until the minimum API >= 21. + // Set a transparent background on `urlTextBox`. The deprecated `.getDrawable()` must be used until the minimum API >= 21. urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent)); } @@ -4692,59 +4691,62 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private void highlightUrlText() { - // Get the URL string. - String urlString = urlTextBox.getText().toString(); - - // Highlight the URL according to the protocol. - if (urlString.startsWith("file://")) { // This is a file URL. - // De-emphasize only the protocol. - urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else if (urlString.startsWith("content://")) { - // De-emphasize only the protocol. - urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // This is a web URL. - // Get the index of the `/` immediately after the domain name. - int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); - - // Create a base URL string. - String baseUrl; - - // Get the base URL. - if (endOfDomainName > 0) { // There is at least one character after the base URL. + // Only highlight the URL text if the box is not currently selected. + if (!urlTextBox.hasFocus()) { + // Get the URL string. + String urlString = urlTextBox.getText().toString(); + + // Highlight the URL according to the protocol. + if (urlString.startsWith("file://")) { // This is a file URL. + // De-emphasize only the protocol. + urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else if (urlString.startsWith("content://")) { + // De-emphasize only the protocol. + urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { // This is a web URL. + // Get the index of the `/` immediately after the domain name. + int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2)); + + // Create a base URL string. + String baseUrl; + // Get the base URL. - baseUrl = urlString.substring(0, endOfDomainName); - } else { // There are no characters after the base URL. - // Set the base URL to be the entire URL string. - baseUrl = urlString; - } + if (endOfDomainName > 0) { // There is at least one character after the base URL. + // Get the base URL. + baseUrl = urlString.substring(0, endOfDomainName); + } else { // There are no characters after the base URL. + // Set the base URL to be the entire URL string. + baseUrl = urlString; + } - // Get the index of the last `.` in the domain. - int lastDotIndex = baseUrl.lastIndexOf("."); + // Get the index of the last `.` in the domain. + int lastDotIndex = baseUrl.lastIndexOf("."); - // Get the index of the penultimate `.` in the domain. - int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1); + // Get the index of the penultimate `.` in the domain. + int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1); - // Markup the beginning of the URL. - if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted. - urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + // Markup the beginning of the URL. + if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted. + urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - // De-emphasize subdomains. - if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. - urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } - } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted. - if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. - // De-emphasize the protocol and the additional subdomains. - urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - } else { // There is only one subdomain in the domain name. - // De-emphasize only the protocol. - urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + // De-emphasize subdomains. + if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. + urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted. + if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name. + // De-emphasize the protocol and the additional subdomains. + urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } else { // There is only one subdomain in the domain name. + // De-emphasize only the protocol. + urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } } - } - // De-emphasize the text after the domain name. - if (endOfDomainName > 0) { - urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + // De-emphasize the text after the domain name. + if (endOfDomainName > 0) { + urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } } } } @@ -4799,4 +4801,185 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook bookmarksTitleTextView.setText(currentBookmarksFolder); } } + + private void openWithApp(String url) { + // Create the open with intent with `ACTION_VIEW`. + Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW); + + // Set the URI but not the MIME type. This should open all available apps. + openWithAppIntent.setData(Uri.parse(url)); + + // Flag the intent to open in a new task. + openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // Show the chooser. + startActivity(openWithAppIntent); + } + + private void openWithBrowser(String url) { + // Create the open with intent with `ACTION_VIEW`. + Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW); + + // Set the URI and the MIME type. `"text/html"` should load browser options. + openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html"); + + // Flag the intent to open in a new task. + openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // Show the chooser. + startActivity(openWithBrowserIntent); + } + + private static void checkPinnedMismatch() { + if ((pinnedSslCertificate || pinnedIpAddresses) && !ignorePinnedDomainInformation) { + // Initialize the current SSL certificate variables. + String currentWebsiteIssuedToCName = ""; + String currentWebsiteIssuedToOName = ""; + String currentWebsiteIssuedToUName = ""; + String currentWebsiteIssuedByCName = ""; + String currentWebsiteIssuedByOName = ""; + String currentWebsiteIssuedByUName = ""; + Date currentWebsiteSslStartDate = null; + Date currentWebsiteSslEndDate = null; + + + // Extract the individual pieces of information from the current website SSL certificate if it is not null. + if (sslCertificate != null) { + currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName(); + currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName(); + currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName(); + currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName(); + currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName(); + currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName(); + currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate(); + currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate(); + } + + // Initialize string variables to store the SSL certificate dates. Strings are needed to compare the values below, which doesn't work with `Dates` if they are `null`. + String currentWebsiteSslStartDateString = ""; + String currentWebsiteSslEndDateString = ""; + String pinnedSslStartDateString = ""; + String pinnedSslEndDateString = ""; + + // Convert the `Dates` to `Strings` if they are not `null`. + if (currentWebsiteSslStartDate != null) { + currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString(); + } + + if (currentWebsiteSslEndDate != null) { + currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString(); + } + + if (pinnedSslStartDate != null) { + pinnedSslStartDateString = pinnedSslStartDate.toString(); + } + + if (pinnedSslEndDate != null) { + pinnedSslEndDateString = pinnedSslEndDate.toString(); + } + + // Check to see if the pinned information matches the current information. + if ((pinnedIpAddresses && !currentHostIpAddresses.equals(pinnedHostIpAddresses)) || (pinnedSslCertificate && (!currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) || + !currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) || !currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) || + !currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) || !currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) || + !currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) || + !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) { + + // Get a handle for the pinned mismatch alert dialog. + AppCompatDialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(pinnedSslCertificate, pinnedIpAddresses); + + // Show the pinned mismatch alert dialog. + pinnedMismatchDialogFragment.show(supportFragmentManager, "Pinned Mismatch"); + } + } + } + + // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `String` contains the results. + private static class GetHostIpAddresses extends AsyncTask { + // The weak references are used to determine if the activity have disappeared while the AsyncTask is running. + private final WeakReference activityWeakReference; + + GetHostIpAddresses(Activity activity) { + // Populate the weak references. + activityWeakReference = new WeakReference<>(activity); + } + + // `onPreExecute()` operates on the UI thread. + @Override + protected void onPreExecute() { + // Get a handle for the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity == null) || activity.isFinishing()) { + return; + } + + // Set the getting IP addresses tracker. + gettingIpAddresses = true; + } + + + @Override + protected String doInBackground(String... domainName) { + // Get a handle for the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity == null) || activity.isFinishing()) { + // Return an empty spannable string builder. + return ""; + } + + // 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. + } + + // Return the string. + return ipAddresses.toString(); + } + + // `onPostExecute()` operates on the UI thread. + @Override + protected void onPostExecute(String ipAddresses) { + // Get a handle for the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity == null) || activity.isFinishing()) { + return; + } + + // Store the IP addresses. + currentHostIpAddresses = ipAddresses; + + if (!urlIsLoading) { + checkPinnedMismatch(); + } + + // Reset the getting IP addresses tracker. + gettingIpAddresses = false; + } + } } \ No newline at end of file