X-Git-Url: https://gitweb.stoutner.com/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Factivities%2FMainWebViewActivity.java;h=975814a65a5814b53ace7490d03be6e7eba06b21;hb=5fb34c1fa70b7c42a0fc3c0b5af8e856d3af2695;hp=06b4c4c4a136a94704b6def631df7bb3e80bfe76;hpb=5bcf4ca90f27512b94fb7aca4fad37b4e4774655;p=PrivacyBrowserAndroid.git 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 06b4c4c4..975814a6 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -34,7 +34,6 @@ import android.content.SharedPreferences; import android.content.res.Configuration; import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -63,7 +62,6 @@ import android.text.Editable; import android.text.Spanned; import android.text.TextWatcher; import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; import android.util.Patterns; import android.view.ContextMenu; import android.view.GestureDetector; @@ -97,6 +95,7 @@ import com.stoutner.privacybrowser.BuildConfig; import com.stoutner.privacybrowser.R; import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog; import com.stoutner.privacybrowser.dialogs.DownloadImageDialog; +import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog; import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog; import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog; import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; @@ -106,6 +105,7 @@ import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog; import java.io.BufferedReader; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; @@ -113,6 +113,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -120,22 +121,22 @@ import java.util.Set; // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21. public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener, - SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener { + PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener { - // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, - // `EditBookmarkFolderDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, `ViewSslCertificateDialog`, `CreateHomeScreenShortcutDialog`, and `OrbotProxyHelper`. - // It is also used in `onCreate()`, `applyAppSettings()`, and `applyDomainSettings()`. + // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, + // `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, `ViewSslCertificateDialog`, + // `CreateHomeScreenShortcutDialog`, and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`. public static boolean darkTheme; - // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `ViewSslCertificateDialog`. - // It is also used in `onCreate()`, `onCreateHomeScreenShortcutCreate()`, and `applyDomainSettings`. + // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, + // and `ViewSslCertificateDialog`. It is also used in `onCreate()`, `onCreateHomeScreenShortcutCreate()`, and `applyDomainSettings`. public static Bitmap favoriteIconBitmap; // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`. // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`. public static String formattedUrlString; - // `sslCertificate` is public static so it can be accessed from `ViewSslCertificateDialog`. It is also used in `onCreate()`. + // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`. public static SslCertificate sslCertificate; // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`. @@ -150,11 +151,23 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `reloadOnRestartBoolean` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()` public static boolean reloadOnRestartBoolean; + // 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 int domainSettingsDatabaseId; + public static boolean pinnedDomainSslCertificate; + 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; + // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, and `applyAppSettings()`. private ActionBar appBar; - // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, and `applyDomainSettings()`. + // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`. private boolean navigatingHistory; // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`. @@ -167,7 +180,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation private CoordinatorLayout rootCoordinatorLayout; // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()` - // and `setDisplayWebpageImages()`. + // `onSslMismatchBack()`, and `setDisplayWebpageImages()`. private WebView mainWebView; // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`. @@ -216,7 +229,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `adBlockerEnabled` is used in `onCreate()` and `applyAppSettings()`. private boolean adBlockerEnabled; - // `privacyBrowserRuntime` is used in `onCreate()` and `applyAppSettings()`. + // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`. private Runtime privacyBrowserRuntime; // `incognitoModeEnabled` is used in `onCreate()` and `applyAppSettings()`. @@ -234,9 +247,12 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`. private boolean translucentNavigationBarOnFullscreen; - // `currentDomainName` is used in `onCreate()`, `onNavigationItemSelected()`, and `applyDomainSettings()`. + // `currentDomainName` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchProceed()`, and `applyDomainSettings()`. private String currentDomainName; + // `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`. + private boolean ignorePinnedSslCertificate; + // `waitingForOrbot` is used in `onCreate()` and `applyAppSettings()`. private boolean waitingForOrbot; @@ -252,7 +268,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `waitingForOrbotData` is used in `onCreate()` and `applyAppSettings()`. private String waitingForOrbotHTMLString; - // `privateDataDirectoryString` is used in `onCreate()` and `onNavigationItemSelected()`. + // `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`. private String privateDataDirectoryString; // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. @@ -282,9 +298,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `finalGrayColorSpam` is used in `onCreate()` and `highlightUrlText()`. private ForegroundColorSpan finalGrayColorSpan; - // `boldStyleSpan` is used in `onCreate()` and `highlightUrlText()`. - private StyleSpan boldStyleSpan; - // `adView` is used in `onCreate()` and `onConfigurationChanged()`. private View adView; @@ -297,6 +310,10 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`. private RelativeLayout mainWebViewRelativeLayout; + // `urlIsLoading` is used in `onCreate()`, `loadUrl()`, and `applyDomainSettings()`. + private boolean urlIsLoading; + + @Override // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers. @SuppressLint("SetJavaScriptEnabled") @@ -341,7 +358,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700)); initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500)); - boldStyleSpan = new StyleSpan(Typeface.BOLD); // Get a handle for `urlTextBox`. urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.url_edittext); @@ -355,13 +371,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation urlTextBox.getText().removeSpan(redColorSpan); urlTextBox.getText().removeSpan(initialGrayColorSpan); urlTextBox.getText().removeSpan(finalGrayColorSpan); - urlTextBox.getText().removeSpan(boldStyleSpan); } else { // The user has stopped editing `urlTextBox`. // Reapply the highlighting. highlightUrlText(); - - // Scroll to the beginning of the text. - urlTextBox.setScrollX(0); } } }); @@ -718,9 +730,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Update the URL in urlTextBox when the page starts to load. @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { - // Reset `webViewTitle` - webViewTitle = getString(R.string.no_title); - // Check to see if we are waiting on Orbot. if (!waitingForOrbot) { // We are not waiting on Orbot, so we need to process the URL. // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded. @@ -736,12 +745,18 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation if (navigatingHistory) { applyDomainSettings(url); } + + // 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; } } // Update formattedUrlString and urlTextBox. It is necessary to do this after the page finishes loading because the final URL can change during load. @Override public void onPageFinished(WebView view, String url) { + // 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) { // Clear the cache. `true` includes disk files. @@ -793,20 +808,100 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } } - // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog`. + // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedSslCertificateMismatchDialog`. 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. + if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate) { + // 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(), getResources().getString(R.string.ssl_certificate_mismatch)); + } + } } } // Handle SSL Certificate errors. @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`. - sslErrorHandler = handler; - - // Display the SSL error `AlertDialog`. - AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error); - sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error)); + // Get the current website SSL certificate. + SslCertificate currentWebsiteSslCertificate = error.getCertificate(); + + // Extract the individual pieces of information from the current website SSL certificate. + String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName(); + String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName(); + String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName(); + String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName(); + String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName(); + String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName(); + Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate(); + 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. + 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()`. + sslErrorHandler = handler; + + // Display the SSL error `AlertDialog`. + AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error); + sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error)); + } } }); @@ -1109,6 +1204,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage); MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies); + MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage); MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size); MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images); @@ -1124,13 +1220,30 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Enable third-party cookies if first-party cookies are enabled. toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled); - // Enable DOM Storage if JavaScript is enabled. + // Enable `DOM Storage` if JavaScript is enabled. toggleDomStorageMenuItem.setEnabled(javaScriptEnabled); - // Enable Clear Cookies if there are any. + // Enable `Clear Cookies` if there are any. clearCookiesMenuItem.setEnabled(cookieManager.hasCookies()); - // Enable Clear Form Data is there is any. + // Get a count of the number of files in the `Local Storage` directory. + File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/"); + int localStorageDirectoryNumberOfFiles = 0; + if (localStorageDirectory.exists()) { + localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length; + } + + // Get a count of the number of files in the `IndexedDB` directory. + File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB"); + int indexedDBDirectoryNumberOfFiles = 0; + if (indexedDBDirectory.exists()) { + indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length; + } + + // Enable `Clear DOM Storage` if there is any. + clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0); + + // Enable `Clear Form Data` is there is any. WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this); clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData()); @@ -1385,6 +1498,13 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Delete the DOM Storage. WebStorage webStorage = WebStorage.getInstance(); webStorage.deleteAllData(); + + // Manually remove `IndexedDB` if it exists. + try { + privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); + } catch (IOException e) { + // Do nothing if an error is thrown. + } } } }) @@ -1465,12 +1585,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation case R.id.share: // Setup the share string. - String shareString; - if (webViewTitle != null) { - shareString = webViewTitle + " – " + urlTextBox.getText().toString(); - } else { - shareString = urlTextBox.getText().toString(); - } + String shareString = webViewTitle + " – " + urlTextBox.getText().toString(); // Create the share intent. Intent shareIntent = new Intent(); @@ -1489,17 +1604,15 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Show the Find on Page `RelativeLayout`. findOnPageLinearLayout.setVisibility(View.VISIBLE); - // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android. - // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working - findOnPageEditText.postDelayed(new Runnable() - { + // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android. http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working + findOnPageEditText.postDelayed(new Runnable() { @Override public void run() { // Set the focus on `findOnPageEditText`. findOnPageEditText.requestFocus(); - // Display the keyboard. + // Display the keyboard. `0` sets no input flags. inputMethodManager.showSoftInput(findOnPageEditText, 0); } }, 200); @@ -1651,10 +1764,16 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation WebStorage webStorage = WebStorage.getInstance(); webStorage.deleteAllData(); - // Manually delete the DOM storage directory, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run. + // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run. try { // We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise. privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"}); + + // We have to use multiple commands because `Runtime.exec()` does not like `*`. + privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB"); + privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager"); + privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal"); + privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases"); } catch (IOException e) { // Do nothing if an error is thrown. } @@ -2075,6 +2194,26 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation sslErrorHandler.proceed(); } + @Override + public void onSslMismatchBack() { + if (mainWebView.canGoBack()) { // There is a back page in the history. + // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded. + navigatingHistory = true; + + // Go back. + mainWebView.goBack(); + } else { // There are no pages to go back to. + // Load a blank page + loadUrl(""); + } + } + + @Override + public void onSslMismatchProceed() { + // Do not check the pinned SSL certificate for this domain again until the domain changes. + ignorePinnedSslCertificate = true; + } + @Override public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) { // Set `navigatingHistory` so that the domain settings are applied when the new URL is loaded. @@ -2166,6 +2305,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Load the URL. mainWebView.loadUrl(url, customHeaders); + + // Set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website. + urlIsLoading = true; } public void findPreviousOnPage(View view) { @@ -2381,6 +2523,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Set the new `hostname` as the `currentDomainName`. currentDomainName = hostName; + // Reset `ignorePinnedSslCertificate`. + ignorePinnedSslCertificate = false; + // Reset `favoriteIconBitmap` and display it in the `appbar`. favoriteIconBitmap = favoriteIconDefaultBitmap; favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true)); @@ -2436,12 +2581,18 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Get a handle for the shared preference. `this` references the current context. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + // Store the default font size and user agent information. + String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); + String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"); + String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); + if (domainSettingsApplied) { // The url we are loading has custom domain settings. // Get a cursor for the current host and move it to the first position. Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase); currentHostDomainSettingsCursor.moveToFirst(); // Get the settings from the cursor. + domainSettingsDatabaseId = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID))); javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1); firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1); thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1); @@ -2450,6 +2601,27 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation String userAgentString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT)); int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE)); 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)); + + // 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; + } else { + pinnedDomainSslStartDate = 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; + } else { + pinnedDomainSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE))); + } // Close `currentHostDomainSettingsCursor`. currentHostDomainSettingsCursor.close(); @@ -2459,20 +2631,50 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation cookieManager.setAcceptCookie(firstPartyCookiesEnabled); mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled); mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); - mainWebView.getSettings().setTextZoom(fontSize); + + // Apply the font size. + if (fontSize == 0) { // Apply the default font size. + mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); + } else { // Apply the specified font size. + mainWebView.getSettings().setTextZoom(fontSize); + } // Set third-party cookies status if API >= 21. if (Build.VERSION.SDK_INT >= 21) { cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled); } - // Set the user agent. - if (userAgentString.equals("WebView default user agent")) { - // Set the user agent to `""`, which uses the default value. - mainWebView.getSettings().setUserAgentString(""); - } else { - // Use the selected user agent. - mainWebView.getSettings().setUserAgentString(userAgentString); + // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload. + if (!urlIsLoading) { + switch (userAgentString) { + case "System default user agent": + // Set the user agent according to the system default. + switch (defaultUserAgentString) { + case "WebView default user agent": + // Set the user agent to `""`, which uses the default value. + mainWebView.getSettings().setUserAgentString(""); + break; + + case "Custom user agent": + // Set the custom user agent. + mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); + break; + + default: + // Use the selected user agent. + mainWebView.getSettings().setUserAgentString(defaultUserAgentString); + } + break; + + case "WebView default user agent": + // Set the user agent to `""`, which uses the default value. + mainWebView.getSettings().setUserAgentString(""); + break; + + default: + // Use the selected user agent. + mainWebView.getSettings().setUserAgentString(userAgentString); + } } // Set a green background on `urlTextBox` to indicate that custom domain settings are being used. We have to use the deprecated `.getDrawable()` until the minimum API >= 21. @@ -2488,9 +2690,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false); domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false); saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false); - String userAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"); - String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); - String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); // Apply the default settings. mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); @@ -2499,27 +2698,40 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); + // Reset the pinned SSL certificate information. + domainSettingsDatabaseId = -1; + pinnedDomainSslCertificate = false; + pinnedDomainSslIssuedToCNameString = ""; + pinnedDomainSslIssuedToONameString = ""; + pinnedDomainSslIssuedToUNameString = ""; + pinnedDomainSslIssuedByCNameString = ""; + pinnedDomainSslIssuedByONameString = ""; + pinnedDomainSslIssuedByUNameString = ""; + pinnedDomainSslStartDate = null; + pinnedDomainSslEndDate = null; + // Set third-party cookies status if API >= 21. if (Build.VERSION.SDK_INT >= 21) { cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled); } - // Set the default user agent. - switch (userAgentString) { - case "WebView default user agent": - // Set the user agent to `""`, which uses the default value. - mainWebView.getSettings().setUserAgentString(""); - break; - - case "Custom user agent": - // Set the custom user agent. - mainWebView.getSettings().setUserAgentString(customUserAgentString); - break; - - default: - // Use the selected user agent. - mainWebView.getSettings().setUserAgentString(userAgentString); - break; + // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload. + if (!urlIsLoading) { + switch (defaultUserAgentString) { + case "WebView default user agent": + // Set the user agent to `""`, which uses the default value. + mainWebView.getSettings().setUserAgentString(""); + break; + + case "Custom user agent": + // Set the custom user agent. + mainWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); + break; + + default: + // Use the selected user agent. + mainWebView.getSettings().setUserAgentString(defaultUserAgentString); + } } // Set a transparent background on `urlTextBox`. We have to use the deprecated `.getDrawable()` until the minimum API >= 21. @@ -2582,23 +2794,39 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation if (firstPartyCookiesEnabled) { // First-party cookies are enabled. firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_enabled); } else { // First-party cookies are disabled. - firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_disabled); + if (darkTheme) { + firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_disabled_dark); + } else { + firstPartyCookiesIconMenuItem.setIcon(R.drawable.cookies_disabled_light); + } } // Update `domStorageIcon`. if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled. domStorageIconMenuItem.setIcon(R.drawable.dom_storage_enabled); } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled. - domStorageIconMenuItem.setIcon(R.drawable.dom_storage_disabled); + if (darkTheme) { + domStorageIconMenuItem.setIcon(R.drawable.dom_storage_disabled_dark); + } else { + domStorageIconMenuItem.setIcon(R.drawable.dom_storage_disabled_light); + } } else { // JavaScript is disabled, so DOM storage is ghosted. - domStorageIconMenuItem.setIcon(R.drawable.dom_storage_ghosted); + if (darkTheme) { + domStorageIconMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark); + } else { + domStorageIconMenuItem.setIcon(R.drawable.dom_storage_ghosted_light); + } } // Update `formDataIcon`. if (saveFormDataEnabled) { // Form data is enabled. formDataIconMenuItem.setIcon(R.drawable.form_data_enabled); } else { // Form data is disabled. - formDataIconMenuItem.setIcon(R.drawable.form_data_disabled); + if (darkTheme) { + formDataIconMenuItem.setIcon(R.drawable.form_data_disabled_dark); + } else { + formDataIconMenuItem.setIcon(R.drawable.form_data_disabled_light); + } } // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`. `this` references the current activity. @@ -2612,7 +2840,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation if (urlString.startsWith("http://")) { // Highlight connections that are not encrypted. urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); - urlTextBox.getText().setSpan(boldStyleSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE); } else if (urlString.startsWith("https://")) { // Highlight connections that are encrypted. urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE); }