From 729652a6a06a8c1bf6244c56089a9c0db84e283e Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Wed, 3 Apr 2019 19:26:52 -0700 Subject: [PATCH] Track URL loading by tab. --- .../activities/DomainsActivity.java | 16 +- .../activities/MainWebViewActivity.java | 321 ++++++------- .../activities/ViewSourceActivity.java | 437 +---------------- .../asynctasks/GetHostIpAddresses.java | 4 +- .../privacybrowser/asynctasks/GetSource.java | 454 ++++++++++++++++++ .../views/NestedScrollWebView.java | 21 + 6 files changed, 632 insertions(+), 621 deletions(-) create mode 100644 app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java index 3f3b9428..26fbd91c 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java @@ -136,18 +136,18 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo // Extract the values from `savedInstanceState` if it is not `null`. if (savedInstanceState != null) { restartAfterRotate = true; - domainSettingsDisplayedBeforeRotate = savedInstanceState.getBoolean("domainSettingsDisplayed"); - domainSettingsDatabaseIdBeforeRotate = savedInstanceState.getInt("domainSettingsDatabaseId"); + domainSettingsDisplayedBeforeRotate = savedInstanceState.getBoolean("domain_settings_displayed"); + domainSettingsDatabaseIdBeforeRotate = savedInstanceState.getInt("domain_settings_database_id"); } // Get the launching intent Intent intent = getIntent(); // Extract the domain to load if there is one. `-1` is the default value. - goDirectlyToDatabaseId = intent.getIntExtra("loadDomain", -1); + goDirectlyToDatabaseId = intent.getIntExtra("load_domain", -1); // Get the status of close-on-back, which is true when the domains activity is called from the options menu. - closeOnBack = intent.getBooleanExtra("closeOnBack", false); + closeOnBack = intent.getBooleanExtra("close_on_back", false); // Set the content view. setContentView(R.layout.domains_coordinatorlayout); @@ -536,11 +536,11 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo saveDomainSettings(coordinatorLayout, resources); // Store `DomainSettingsDisplayed`. - outState.putBoolean("domainSettingsDisplayed", true); - outState.putInt("domainSettingsDatabaseId", DomainSettingsFragment.databaseId); + outState.putBoolean("domain_settings_displayed", true); + outState.putInt("domain_settings_database_id", DomainSettingsFragment.databaseId); } else { // `DomainSettingsFragment` is not displayed. - outState.putBoolean("domainSettingsDisplayed", false); - outState.putInt("domainSettingsDatabaseId", -1); + outState.putBoolean("domain_settings_displayed", false); + outState.putInt("domain_settings_database_id", -1); } super.onSaveInstanceState(outState); 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 fd357a6a..e0a310c8 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -181,18 +181,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`. public static String formattedUrlString; - // TODO. We are going to have to move this to the NestedScrollWebView. - // The URL loading tracker is public static so it can be accessed from `GetHostIpAddresses`. - // It is also used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, `applyDomainSettings()`, and `GetHostIpAddresses`. - public static boolean urlIsLoading; - // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`. public static String orbotStatus; - // TODO. - // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`. It is also used in `applyDomainSettings()`. - public static String appliedUserAgentString; - // The WebView pager adapter is accessed from `PinnedMismatchDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`. public static WebViewPagerAdapter webViewPagerAdapter; @@ -246,9 +237,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`. private final Map customHeaders = new HashMap<>(); - // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`. - private boolean javaScriptEnabled; - // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`. private boolean firstPartyCookiesEnabled; @@ -271,14 +259,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `searchURL` is used in `loadURLFromTextBox()` and `applyProxyThroughOrbot()`. private String searchURL; - // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`. - private Menu mainMenu; + // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()` and `updatePrivacyIcons()`. + private Menu optionsMenu; - // `refreshMenuItem` is used in `onCreate()` and `onCreateOptionsMenu()`. + // The refresh menu item is set in `onCreateOptionsMenu()` and accessed from `initializeWebView()`. + // It must be this way because `initializeWebView()` runs before the menu is created but doesn't actually modify the menu until later. private MenuItem refreshMenuItem; // The navigation requests menu item is used in `onCreate()` and accessed from `WebViewPagerAdapter`. - private MenuItem navigationRequestsMenuItem; + private MenuItem navigationRequestsMenuItem; // TODO. // TODO. This could probably be removed. // The blocklist helper is used in `onCreate()` and `WebViewPagerAdapter`. @@ -310,9 +299,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`. private String webViewDefaultUserAgent; - // `defaultCustomUserAgentString` is used in `onPrepareOptionsMenu()` and `applyDomainSettings()`. - private String defaultCustomUserAgentString; - // `privacyBrowserRuntime` is used in `onCreate()`, `onOptionsItemSelected()`, and `applyAppSettings()`. private Runtime privacyBrowserRuntime; @@ -343,9 +329,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `downloadWithExternalApp` is used in `onCreate()`, `onCreateContextMenu()`, and `applyDomainSettings()`. private boolean downloadWithExternalApp; - // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`. - private String currentDomainName; - // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`. private BroadcastReceiver orbotStatusBroadcastReceiver; @@ -428,11 +411,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2; @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. - // Also, remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`. - @SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"}) - // Remove Android Studio's warning about deprecations. The deprecated `getColor()` must be used until API >= 23. - @SuppressWarnings("deprecation") + // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`. + @SuppressLint("ClickableViewAccessibility") protected void onCreate(Bundle savedInstanceState) { // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); @@ -516,8 +496,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set `waitingForOrbotHTMLString`. waitingForOrbotHtmlString = "

" + getString(R.string.waiting_for_orbot) + "

"; - // Initialize `currentDomainName`, `orbotStatus`, and `waitingForOrbot`. - currentDomainName = ""; + // Initialize the Orbot status and the waiting for Orbot trackers. orbotStatus = "unknown"; waitingForOrbot = false; @@ -614,6 +593,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Store the current WebView. currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview); + // Update the privacy icons. `true` redraws the icons in the app bar. + updatePrivacyIcons(true); + // Store the current formatted URL string. formattedUrlString = currentWebView.getUrl(); @@ -638,7 +620,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook urlEditText.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green)); } } else { - urlEditText.setBackgroundDrawable(getResources().getDrawable(R.color.transparent)); + urlEditText.setBackground(getResources().getDrawable(R.color.transparent)); } // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled via swiping in the view pager. @@ -926,7 +908,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook inFullScreenBrowsingMode = false; // Initialize the privacy settings variables. - javaScriptEnabled = false; firstPartyCookiesEnabled = false; thirdPartyCookiesEnabled = false; domStorageEnabled = false; @@ -1229,8 +1210,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Inflate the menu. This adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.webview_options_menu, menu); - // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`. - mainMenu = menu; + // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`. + optionsMenu = menu; // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step. updatePrivacyIcons(false); @@ -1279,7 +1260,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Replace Refresh with Stop if a URL is already loading. - if (urlIsLoading) { + if (currentWebView != null && currentWebView.getProgress() != 100) { // Set the title. refreshMenuItem.setTitle(R.string.stop); @@ -1369,7 +1350,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook toggleThirdPartyCookiesMenuItem.setEnabled(firstPartyCookiesEnabled); // Enable DOM Storage if JavaScript is enabled. - toggleDomStorageMenuItem.setEnabled(javaScriptEnabled); + toggleDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled()); // Enable Clear Cookies if there are any. clearCookiesMenuItem.setEnabled(cookieManager.hasCookies()); @@ -1524,20 +1505,20 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the selected menu item ID. int menuItemId = menuItem.getItemId(); + // Get a handle for the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + // Run the commands that correlate to the selected menu item. switch (menuItemId) { case R.id.toggle_javascript: - // Switch the status of javaScriptEnabled. - javaScriptEnabled = !javaScriptEnabled; - - // Apply the new JavaScript status. - currentWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); + // Toggle the JavaScript status. + currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled()); // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. updatePrivacyIcons(true); // Display a `Snackbar`. - if (javaScriptEnabled) { // JavaScrip is enabled. + if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled. Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show(); } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled. Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show(); @@ -1553,7 +1534,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings. // Reapply the domain settings on returning to `MainWebViewActivity`. reapplyDomainSettingsOnRestart = true; - currentDomainName = ""; + currentWebView.resetCurrentDomainName(); // TODO. Move these to `putExtra`. The certificate can be stored as strings. // Store the current SSL certificate and IP addresses in the domains activity. @@ -1564,15 +1545,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Intent domainsIntent = new Intent(this, DomainsActivity.class); // Put extra information instructing the domains activity to directly load the current domain and close on back instead of returning to the domains list. - domainsIntent.putExtra("loadDomain", currentWebView.getDomainSettingsDatabaseId()); - domainsIntent.putExtra("closeOnBack", true); + domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId()); + domainsIntent.putExtra("close_on_back", true); // Make it so. startActivity(domainsIntent); } else { // Add a new domain. // Apply the new domain settings on returning to `MainWebViewActivity`. reapplyDomainSettingsOnRestart = true; - currentDomainName = ""; + currentWebView.resetCurrentDomainName(); // Get the current domain Uri currentUri = Uri.parse(formattedUrlString); @@ -1593,8 +1574,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook Intent domainsIntent = new Intent(this, DomainsActivity.class); // Put extra information instructing the domains activity to directly load the new domain and close on back instead of returning to the domains list. - domainsIntent.putExtra("loadDomain", newDomainDatabaseId); - domainsIntent.putExtra("closeOnBack", true); + domainsIntent.putExtra("load_domain", newDomainDatabaseId); + domainsIntent.putExtra("close_on_back", true); // Make it so. startActivity(domainsIntent); @@ -1617,7 +1598,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Display a `Snackbar`. if (firstPartyCookiesEnabled) { // First-party cookies are enabled. Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); - } else if (javaScriptEnabled) { // JavaScript is still enabled. + } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled. Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); } else { // Privacy mode. Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); @@ -1840,7 +1821,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook menuItem.setChecked(fanboysAnnoyanceListEnabled); // Update the staus of Fanboy's Social Blocking List. - MenuItem fanboysSocialBlockingListMenuItem = mainMenu.findItem(R.id.fanboys_social_blocking_list); + MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list); fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListEnabled); // Reload the current WebView. @@ -1978,7 +1959,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.user_agent_custom: // Update the user agent. - currentWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); + currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); // Reload the current WebView. currentWebView.reload(); @@ -2042,23 +2023,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nightMode = !nightMode; // Enable or disable JavaScript according to night mode, the global preference, and any domain settings. - if (nightMode) { // Night mode is enabled. Enable JavaScript. - // Update the global variable. - javaScriptEnabled = true; + if (nightMode) { // Night mode is enabled, which requires JavaScript. + // Enable JavaScript. + currentWebView.getSettings().setJavaScriptEnabled(true); } else if (currentWebView.getDomainSettingsApplied()) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings. - // Get the JavaScript preference that was stored the last time domain settings were loaded. - javaScriptEnabled = domainSettingsJavaScriptEnabled; + // Apply the JavaScript preference that was stored the last time domain settings were loaded. + currentWebView.getSettings().setJavaScriptEnabled(domainSettingsJavaScriptEnabled); } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference. - // Get a handle for the shared preference. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Get the JavaScript preference. - javaScriptEnabled = sharedPreferences.getBoolean("javascript", false); + // Apply the JavaScript preference. + currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false)); } - // Apply the JavaScript setting to the WebView. - currentWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); - // Update the privacy icons. updatePrivacyIcons(false); @@ -2089,8 +2064,13 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook return true; case R.id.view_source: - // Launch the View Source activity. + // Create an intent to launch the view source activity. Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class); + + // Add the user agent as an extra to the intent. + viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString()); + + // Make it so. startActivity(viewSourceIntent); return true; @@ -2426,7 +2406,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook case R.id.domains: // Set the flag to reapply the domain settings on restart when returning from Domain Settings. reapplyDomainSettingsOnRestart = true; - currentDomainName = ""; + currentWebView.resetCurrentDomainName(); // TODO. Do this for all tabs. // TODO. Move these to `putExtra`. The certificate can be stored as strings. // Store the current SSL certificate and IP addresses in the domains activity. @@ -2444,7 +2424,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set the flag to reapply the domain settings on restart when returning from Settings. reapplyDomainSettingsOnRestart = true; - currentDomainName = ""; + currentWebView.resetCurrentDomainName(); // TODO. Do this for all tabs. // Launch the settings activity. Intent settingsIntent = new Intent(this, SettingsActivity.class); @@ -3214,12 +3194,12 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } @Override - public void onSslErrorCancel() { + public void onSslErrorCancel() { // TODO. How to handle this with multiple tabs? There could be multiple errors at once. sslErrorHandler.cancel(); } @Override - public void onSslErrorProceed() { + public void onSslErrorProceed() { // TODO. How to handle this with multiple tabs? There could be multiple errors at once. sslErrorHandler.proceed(); } @@ -3388,9 +3368,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Apply the domain settings. applyDomainSettings(currentWebView, url, true, false); - // If loading a website, set `urlIsLoading` to prevent changes in the user agent on websites with redirects from reloading the current website. - urlIsLoading = !url.equals(""); - // Load the URL. currentWebView.loadUrl(url, customHeaders); } @@ -3521,45 +3498,29 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled. - // The deprecated `.getDrawable()` must be used until the minimum API >= 21. - @SuppressWarnings("deprecation") + @SuppressLint("SetJavaScriptEnabled") private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetFavoriteIcon, boolean reloadWebsite) { // Get a handle for the URL edit text. EditText urlEditText = findViewById(R.id.url_edittext); - // Get the current user agent. + // Store a copy of the current user agent to track changes for the return boolean. String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString(); - // Initialize a variable to track if the user agent changes. - boolean userAgentChanged = false; - // Parse the URL into a URI. Uri uri = Uri.parse(url); // Extract the domain from `uri`. - String hostName = uri.getHost(); - - // Initialize `loadingNewDomainName`. - boolean loadingNewDomainName; - - // If either `hostName` or `currentDomainName` are `null`, run the options for loading a new domain name. - // The lint suggestion to simplify the `if` statement is incorrect, because `hostName.equals(currentDomainName)` can produce a `null object reference.` - //noinspection SimplifiableIfStatement - if ((hostName == null) || (currentDomainName == null)) { // TODO. - loadingNewDomainName = true; - } else { // Determine if `hostName` equals `currentDomainName`. - loadingNewDomainName = !hostName.equals(currentDomainName); // TODO. - } + String newHostName = uri.getHost(); // Strings don't like to be null. - if (hostName == null) { - hostName = ""; + if (newHostName == null) { + newHostName = ""; } // Only apply the domain settings if a new domain is being loaded. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc. - if (loadingNewDomainName) { - // Set the new `hostname` as the `currentDomainName`. - currentDomainName = hostName; // TODO. + if (!nestedScrollWebView.getCurrentDomainName().equals(newHostName)) { + // Set the new host name as the current domain name. + nestedScrollWebView.setCurrentDomainName(newHostName); // Reset the ignoring of pinned domain information. nestedScrollWebView.setIgnorePinnedDomainInformation(false); @@ -3626,9 +3587,9 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook String domainNameInDatabase = null; // Check the hostname against the domain settings set. - if (domainSettingsSet.contains(hostName)) { // The hostname is contained in the domain settings set. + if (domainSettingsSet.contains(newHostName)) { // The hostname is contained in the domain settings set. // Record the domain name in the database. - domainNameInDatabase = hostName; + domainNameInDatabase = newHostName; // Set the domain settings applied tracker to true. nestedScrollWebView.setDomainSettingsApplied(true); @@ -3638,31 +3599,33 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Check all the subdomains of the host name against wildcard domains in the domain cursor. - while (!nestedScrollWebView.getDomainSettingsApplied() && hostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name. - if (domainSettingsSet.contains("*." + hostName)) { // Check the host name prepended by `*.`. + while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name. + if (domainSettingsSet.contains("*." + newHostName)) { // Check the host name prepended by `*.`. // Set the domain settings applied tracker to true. nestedScrollWebView.setDomainSettingsApplied(true); // Store the applied domain names as it appears in the database. - domainNameInDatabase = "*." + hostName; + domainNameInDatabase = "*." + newHostName; } // Strip out the lowest subdomain of of the host name. - hostName = hostName.substring(hostName.indexOf(".") + 1); + newHostName = newHostName.substring(newHostName.indexOf(".") + 1); } - // Get a handle for the shared preference. + // Get a handle for the shared preferences. SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Store the general preference information. String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value)); String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value)); - defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)); // TODO. boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true); nightMode = sharedPreferences.getBoolean("night_mode", false); // TODO. boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true); + // Declare the JavaScript tracker. + boolean javaScriptEnabled; + if (nestedScrollWebView.getDomainSettingsApplied()) { // The url has custom domain settings. // Get a cursor for the current host and move it to the first position. Cursor currentHostDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase); @@ -3670,7 +3633,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Get the settings from the cursor. nestedScrollWebView.setDomainSettingsDatabaseId(currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID))); - javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1); // TODO. + javaScriptEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1); // TODO. Rename to domainSettingsJavaScriptEnabled after the global variable is removed. firstPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1); // TODO. thirdPartyCookiesEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1); // TODO. domStorageEnabled = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1); // TODO. @@ -3743,14 +3706,17 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Enable JavaScript if night mode is enabled. if (nightMode) { - javaScriptEnabled = true; // TODO. + // Enable JavaScript. + nestedScrollWebView.getSettings().setJavaScriptEnabled(true); + } else { + // Set JavaScript according to the domain settings. + nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); } // Close `currentHostDomainSettingsCursor`. currentHostDomainSettingsCursor.close(); // Apply the domain settings. - nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); // TODO. cookieManager.setAcceptCookie(firstPartyCookiesEnabled); //TODO This could be bad. nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled); // TODO. @@ -3773,7 +3739,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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) { // TODO. We need to track this by WebView. + if (nestedScrollWebView.getProgress() == 100) { // A URL is not loading. // Set the user agent. if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent. // Get the array position of the default user agent name. @@ -3792,8 +3758,8 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook break; case SETTINGS_CUSTOM_USER_AGENT: - // Set the custom user agent. - nestedScrollWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); + // Set the default custom user agent. + nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); break; default: @@ -3819,12 +3785,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); } } - - // Store the applied user agent string, which is used in the View Source activity. - appliedUserAgentString = nestedScrollWebView.getSettings().getUserAgentString(); // TODO. - - // Update the user agent change tracker. - userAgentChanged = !appliedUserAgentString.equals(initialUserAgent); // TODO. } // Set swipe to refresh. @@ -3881,11 +3841,14 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`. if (nightMode) { - javaScriptEnabled = true; // TODO. + // Enable JavaScript. + nestedScrollWebView.getSettings().setJavaScriptEnabled(true); + } else { + // Set JavaScript according to the domain settings. + nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); } // Apply the default settings. - nestedScrollWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); // TODO. cookieManager.setAcceptCookie(firstPartyCookiesEnabled); // TODO. nestedScrollWebView.getSettings().setDomStorageEnabled(domStorageEnabled); // TODO. nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); @@ -3906,7 +3869,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // 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) { // TODO. + if (nestedScrollWebView.getProgress() == 100) { // A URL is not loading. // Get the array position of the user agent name. int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName); @@ -3923,36 +3886,28 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook break; case SETTINGS_CUSTOM_USER_AGENT: - // Set the custom user agent. - nestedScrollWebView.getSettings().setUserAgentString(defaultCustomUserAgentString); // TODO. + // Set the default custom user agent. + nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value))); break; default: // Get the user agent string from the user agent data array nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]); } - - // Store the applied user agent string, which is used in the View Source activity. - appliedUserAgentString = nestedScrollWebView.getSettings().getUserAgentString(); // TODO. - - // Update the user agent change tracker. - userAgentChanged = !appliedUserAgentString.equals(initialUserAgent); // TODO. } // Set the loading of webpage images. nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages); - // Set a transparent background on URL edit text. The deprecated `.getDrawable()` must be used until the minimum API >= 21. - urlEditText.setBackgroundDrawable(getResources().getDrawable(R.color.transparent)); + // Set a transparent background on URL edit text. The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21. + urlEditText.setBackground(getResources().getDrawable(R.color.transparent)); } // Close the domains database helper. domainsDatabaseHelper.close(); - // Update the privacy icons, but only if the options menu has already been populated. - if (mainMenu != null) { // TODO. Consider renaming this to optionsMenu. - updatePrivacyIcons(true); - } + // Update the privacy icons. + updatePrivacyIcons(true); } // Reload the website if returning from the Domains activity. @@ -3961,7 +3916,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } // Return the user agent changed status. - return userAgentChanged; + return !nestedScrollWebView.getSettings().getUserAgentString().equals(initialUserAgent); } private void applyProxyThroughOrbot(boolean reloadWebsite) { @@ -4076,59 +4031,62 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook } private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) { - // Get handles for the menu items. - MenuItem privacyMenuItem = mainMenu.findItem(R.id.toggle_javascript); - MenuItem firstPartyCookiesMenuItem = mainMenu.findItem(R.id.toggle_first_party_cookies); - MenuItem domStorageMenuItem = mainMenu.findItem(R.id.toggle_dom_storage); - MenuItem refreshMenuItem = mainMenu.findItem(R.id.refresh); - - // Update the privacy icon. - if (javaScriptEnabled) { // JavaScript is enabled. - privacyMenuItem.setIcon(R.drawable.javascript_enabled); - } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled. - privacyMenuItem.setIcon(R.drawable.warning); - } else { // All the dangerous features are disabled. - privacyMenuItem.setIcon(R.drawable.privacy_mode); - } + // Only update the privacy icons if the options menu has already been populated. + if (optionsMenu != null) { + // Get handles for the menu items. + MenuItem privacyMenuItem = optionsMenu.findItem(R.id.toggle_javascript); + MenuItem firstPartyCookiesMenuItem = optionsMenu.findItem(R.id.toggle_first_party_cookies); + MenuItem domStorageMenuItem = optionsMenu.findItem(R.id.toggle_dom_storage); + MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh); + + // Update the privacy icon. + if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled. + privacyMenuItem.setIcon(R.drawable.javascript_enabled); + } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled. + privacyMenuItem.setIcon(R.drawable.warning); + } else { // All the dangerous features are disabled. + privacyMenuItem.setIcon(R.drawable.privacy_mode); + } - // Update the first-party cookies icon. - if (firstPartyCookiesEnabled) { // First-party cookies are enabled. - firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled); - } else { // First-party cookies are disabled. - if (darkTheme) { - firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark); - } else { - firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light); + // Update the first-party cookies icon. + if (firstPartyCookiesEnabled) { // First-party cookies are enabled. + firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled); + } else { // First-party cookies are disabled. + if (darkTheme) { + firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark); + } else { + firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light); + } } - } - // Update the DOM storage icon. - if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled. - domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled); - } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled. - if (darkTheme) { - domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark); - } else { - domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light); + // Update the DOM storage icon. + if (currentWebView.getSettings().getJavaScriptEnabled() && domStorageEnabled) { // Both JavaScript and DOM storage are enabled. + domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled); + } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled but DOM storage is disabled. + if (darkTheme) { + domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark); + } else { + domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light); + } + } else { // JavaScript is disabled, so DOM storage is ghosted. + if (darkTheme) { + domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark); + } else { + domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light); + } } - } else { // JavaScript is disabled, so DOM storage is ghosted. + + // Update the refresh icon. if (darkTheme) { - domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark); + refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark); } else { - domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light); + refreshMenuItem.setIcon(R.drawable.refresh_enabled_light); } - } - // Update the refresh icon. - if (darkTheme) { - refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark); - } else { - refreshMenuItem.setIcon(R.drawable.refresh_enabled_light); - } - - // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`. - if (runInvalidateOptionsMenu) { - invalidateOptionsMenu(); + // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`. + if (runInvalidateOptionsMenu) { + invalidateOptionsMenu(); + } } } @@ -5083,10 +5041,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook @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 resource requests. nestedScrollWebView.clearResourceRequests(); @@ -5246,9 +5200,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView); } } - - // 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. diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java index 052750b8..bcb94c18 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java @@ -22,27 +22,17 @@ package com.stoutner.privacybrowser.activities; import android.app.Activity; import android.app.DialogFragment; import android.content.Context; -import android.content.SharedPreferences; -import android.graphics.Typeface; -import android.os.AsyncTask; -import android.os.Build; +import android.content.Intent; import android.os.Bundle; -import android.os.LocaleList; -import android.preference.PreferenceManager; -import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; -import android.webkit.CookieManager; import android.widget.EditText; -import android.widget.ProgressBar; -import android.widget.TextView; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; @@ -51,17 +41,9 @@ import androidx.core.app.NavUtils; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.asynctasks.GetSource; import com.stoutner.privacybrowser.dialogs.AboutViewSourceDialog; -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.ref.WeakReference; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Locale; - public class ViewSourceActivity extends AppCompatActivity { // `activity` is used in `onCreate()` and `goBack()`. private Activity activity; @@ -88,6 +70,12 @@ public class ViewSourceActivity extends AppCompatActivity { // Run the default commands. super.onCreate(savedInstanceState); + // Get the launching intent + Intent intent = getIntent(); + + // Get the user agent. + String userAgent = intent.getStringExtra("user_agent"); + // Store a handle for the current activity. activity = this; @@ -165,7 +153,7 @@ public class ViewSourceActivity extends AppCompatActivity { // Get new source data for the current URL if it beings with `http`. if (url.startsWith("http")) { - new GetSource(this).execute(url); + new GetSource(this, userAgent).execute(url); } // Consume the key press. @@ -184,7 +172,7 @@ public class ViewSourceActivity extends AppCompatActivity { // Get new source data for the URL if it begins with `http`. if (url.startsWith("http")) { - new GetSource(this).execute(url); + new GetSource(this, userAgent).execute(url); } else { // Stop the refresh animation. swipeRefreshLayout.setRefreshing(false); @@ -201,7 +189,7 @@ public class ViewSourceActivity extends AppCompatActivity { // Get the source using an AsyncTask if the URL begins with `http`. if (formattedUrlString.startsWith("http")) { - new GetSource(this).execute(formattedUrlString); + new GetSource(this, userAgent).execute(formattedUrlString); } } @@ -291,407 +279,4 @@ public class ViewSourceActivity extends AppCompatActivity { } } } - - // `String` declares the parameters. `Void` does not declare progress units. `SpannableStringBuilder[]` contains the results. - private static class GetSource extends AsyncTask { - // Create a weak reference to the calling activity. - private WeakReference activityWeakReference; - - // Populate the weak reference to the calling activity. - GetSource(Activity activity) { - activityWeakReference = new WeakReference<>(activity); - } - - // `onPreExecute()` operates on the UI thread. - @Override - protected void onPreExecute() { - // Get a handle for the activity. - Activity viewSourceActivity = activityWeakReference.get(); - - // Abort if the activity is gone. - if ((viewSourceActivity == null) || viewSourceActivity.isFinishing()) { - return; - } - - // Get a handle for the progress bar. - ProgressBar progressBar = viewSourceActivity.findViewById(R.id.progress_bar); - - // Make the progress bar visible. - progressBar.setVisibility(View.VISIBLE); - - // Set the progress bar to be indeterminate. - progressBar.setIndeterminate(true); - } - - @Override - protected SpannableStringBuilder[] doInBackground(String... formattedUrlString) { - // Initialize the response body String. - SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder(); - SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder(); - SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder(); - SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder(); - - // Get a handle for the activity. - Activity activity = activityWeakReference.get(); - - // Abort if the activity is gone. - if ((activity == null) || activity.isFinishing()) { - return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder}; - } - - // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`. - try { - // Get the current URL from the main activity. - URL url = new URL(formattedUrlString[0]); - - // Open a connection to the URL. No data is actually sent at this point. - HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(); - - // Instantiate the variables necessary to build the request headers. - requestHeadersBuilder = new SpannableStringBuilder(); - int oldRequestHeadersBuilderLength; - int newRequestHeadersBuilderLength; - - - // Set the `Host` header property. - httpUrlConnection.setRequestProperty("Host", url.getHost()); - - // Add the `Host` header to the string builder and format the text. - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.append("Host"); - newRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - requestHeadersBuilder.append(": "); - requestHeadersBuilder.append(url.getHost()); - - - // Set the `Connection` header property. - httpUrlConnection.setRequestProperty("Connection", "keep-alive"); - - // Add the `Connection` header to the string builder and format the text. - requestHeadersBuilder.append(System.getProperty("line.separator")); - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.append("Connection"); - newRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - requestHeadersBuilder.append(": keep-alive"); - - - // Get the current `User-Agent` string. - String userAgentString = MainWebViewActivity.appliedUserAgentString; - - // Set the `User-Agent` header property. - httpUrlConnection.setRequestProperty("User-Agent", userAgentString); - - // Add the `User-Agent` header to the string builder and format the text. - requestHeadersBuilder.append(System.getProperty("line.separator")); - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.append("User-Agent"); - newRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - requestHeadersBuilder.append(": "); - requestHeadersBuilder.append(userAgentString); - - - // Set the `Upgrade-Insecure-Requests` header property. - httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1"); - - // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text. - requestHeadersBuilder.append(System.getProperty("line.separator")); - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.append("Upgrade-Insecure_Requests"); - newRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - requestHeadersBuilder.append(": 1"); - - - // Set the `x-requested-with` header property. - httpUrlConnection.setRequestProperty("x-requested-with", ""); - - // Add the `x-requested-with` header to the string builder and format the text. - requestHeadersBuilder.append(System.getProperty("line.separator")); - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.append("x-requested-with"); - newRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - requestHeadersBuilder.append(": "); - - - // Get a handle for the shared preferences. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); - - // Only populate `Do Not Track` if it is enabled. - if (sharedPreferences.getBoolean("do_not_track", false)) { - // Set the `dnt` header property. - httpUrlConnection.setRequestProperty("dnt", "1"); - - // Add the `dnt` header to the string builder and format the text. - requestHeadersBuilder.append(System.getProperty("line.separator")); - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - requestHeadersBuilder.append("dnt", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.append("dnt"); - newRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - requestHeadersBuilder.append(": 1"); - } - - - // Set the `Accept` header property. - httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"); - - // Add the `Accept` header to the string builder and format the text. - requestHeadersBuilder.append(System.getProperty("line.separator")); - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.append("Accept"); - newRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - requestHeadersBuilder.append(": "); - requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"); - - - // Instantiate a locale string. - String localeString; - - // Populate the locale string. - if (Build.VERSION.SDK_INT >= 24) { // SDK >= 24 has a list of locales. - // Get the list of locales. - LocaleList localeList = activity.getResources().getConfiguration().getLocales(); - - // Initialize a string builder to extract the locales from the list. - StringBuilder localesStringBuilder = new StringBuilder(); - - // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages. - int q = 10; - - // Populate the string builder with the contents of the locales list. - for (int i = 0; i < localeList.size(); i++) { - // Append a comma if there is already an item in the string builder. - if (i > 0) { - localesStringBuilder.append(","); - } - - // Get the indicated locale from the list. - localesStringBuilder.append(localeList.get(i)); - - // If not the first locale, append `;q=0.i`, which drops by .1 for each removal from the main locale. - if (q < 10) { - localesStringBuilder.append(";q=0."); - localesStringBuilder.append(q); - } - - // Decrement `q`. - q--; - } - - // Store the populated string builder in the locale string. - localeString = localesStringBuilder.toString(); - } else { // SDK < 24 only has a primary locale. - // Store the locale in the locale string. - localeString = Locale.getDefault().toString(); - } - - // Set the `Accept-Language` header property. - httpUrlConnection.setRequestProperty("Accept-Language", localeString); - - // Add the `Accept-Language` header to the string builder and format the text. - requestHeadersBuilder.append(System.getProperty("line.separator")); - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.append("Accept-Language"); - newRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - requestHeadersBuilder.append(": "); - requestHeadersBuilder.append(localeString); - - - // Get the cookies for the current domain. - String cookiesString = CookieManager.getInstance().getCookie(url.toString()); - - // Only process the cookies if they are not null. - if (cookiesString != null) { - // Set the `Cookie` header property. - httpUrlConnection.setRequestProperty("Cookie", cookiesString); - - // Add the `Cookie` header to the string builder and format the text. - requestHeadersBuilder.append(System.getProperty("line.separator")); - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.append("Cookie"); - newRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - requestHeadersBuilder.append(": "); - requestHeadersBuilder.append(cookiesString); - } - - - // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default. If the property is manually set, than `HttpUrlConnection` does not process the decoding. - // Add the `Accept-Encoding` header to the string builder and format the text. - requestHeadersBuilder.append(System.getProperty("line.separator")); - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.append("Accept-Encoding"); - newRequestHeadersBuilderLength = requestHeadersBuilder.length(); - requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - requestHeadersBuilder.append(": gzip"); - - - // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block. - try { - // Initialize the string builders. - responseMessageBuilder = new SpannableStringBuilder(); - responseHeadersBuilder = new SpannableStringBuilder(); - - // Get the response code, which causes the connection to the server to be made. - int responseCode = httpUrlConnection.getResponseCode(); - - // Populate the response message string builder. - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - responseMessageBuilder.append(String.valueOf(responseCode)); - int newLength = responseMessageBuilder.length(); - responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - responseMessageBuilder.append(": "); - responseMessageBuilder.append(httpUrlConnection.getResponseMessage()); - - // Initialize the iteration variable. - int i = 0; - - // Iterate through the received header fields. - while (httpUrlConnection.getHeaderField(i) != null) { - // Add a new line if there is already information in the string builder. - if (i > 0) { - responseHeadersBuilder.append(System.getProperty("line.separator")); - } - - // Add the header to the string builder and format the text. - if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. - responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { // Older versions not so much. - int oldLength = responseHeadersBuilder.length(); - responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i)); - int newLength = responseHeadersBuilder.length(); - responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength + 1, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - responseHeadersBuilder.append(": "); - responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i)); - - // Increment the iteration variable. - i++; - } - - // Instantiate an input stream for the response body. - InputStream inputStream; - - // Get the correct input stream based on the response code. - if (responseCode == 404) { // Get the error stream. - inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream()); - } else { // Get the response body stream. - inputStream = new BufferedInputStream(httpUrlConnection.getInputStream()); - } - - // Initialize the byte array output stream and the conversion buffer byte array. - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - byte[] conversionBufferByteArray = new byte[1024]; - - // Instantiate the variable to track the buffer length. - int bufferLength; - - try { - // Attempt to read data from the input stream and store it in the conversion buffer byte array. Also store the amount of data transferred in the buffer length variable. - while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) { // Proceed while the amount of data stored in the buffer is > 0. - // Write the contents of the conversion buffer to the byte array output stream. - byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength); - } - } catch (IOException e) { - e.printStackTrace(); - } - - // Close the input stream. - inputStream.close(); - - // Populate the response body string with the contents of the byte array output stream. - responseBodyBuilder.append(byteArrayOutputStream.toString()); - } finally { - // Disconnect `httpUrlConnection`. - httpUrlConnection.disconnect(); - } - } catch (IOException e) { - e.printStackTrace(); - } - - // Return the response body string as the result. - return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder}; - } - - // `onPostExecute()` operates on the UI thread. - @Override - protected void onPostExecute(SpannableStringBuilder[] viewSourceStringArray){ - // Get a handle for the activity. - Activity activity = activityWeakReference.get(); - - // Abort if the activity is gone. - if ((activity == null) || activity.isFinishing()) { - return; - } - - // Get handles for the text views. - TextView requestHeadersTextView = activity.findViewById(R.id.request_headers); - TextView responseMessageTextView = activity.findViewById(R.id.response_message); - TextView responseHeadersTextView = activity.findViewById(R.id.response_headers); - TextView responseBodyTextView = activity.findViewById(R.id.response_body); - ProgressBar progressBar = activity.findViewById(R.id.progress_bar); - SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.view_source_swiperefreshlayout); - - // Populate the text views. This can take a long time, and freeze the user interface, if the response body is particularly large. - requestHeadersTextView.setText(viewSourceStringArray[0]); - responseMessageTextView.setText(viewSourceStringArray[1]); - responseHeadersTextView.setText(viewSourceStringArray[2]); - responseBodyTextView.setText(viewSourceStringArray[3]); - - // Hide the progress bar. - progressBar.setIndeterminate(false); - progressBar.setVisibility(View.GONE); - - //Stop the swipe to refresh indicator if it is running - swipeRefreshLayout.setRefreshing(false); - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetHostIpAddresses.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetHostIpAddresses.java index 3c7f37cf..66111de7 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetHostIpAddresses.java +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetHostIpAddresses.java @@ -104,8 +104,8 @@ public class GetHostIpAddresses extends AsyncTask { // Store the IP addresses. nestedScrollWebView.setCurrentIpAddresses(ipAddresses); - //TODO. Move `urlIsLoading` to the WebView. - if (!MainWebViewActivity.urlIsLoading && !nestedScrollWebView.ignorePinnedDomainInformation() && (nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses())) { + // Checked for pinned mismatches if the WebView is not loading a URL, pinned information is not ignored, and there is pinned information. + if ((nestedScrollWebView.getProgress() == 100) && !nestedScrollWebView.ignorePinnedDomainInformation() && (nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses())) { CheckPinnedMismatchHelper.checkPinnedMismatch(fragmentManager, nestedScrollWebView); } } diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java new file mode 100644 index 00000000..680fb7f8 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java @@ -0,0 +1,454 @@ +/* + * Copyright © 2017-2019 Soren Stoutner . + * + * This file is part of Privacy Browser . + * + * Privacy Browser is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser. If not, see . + */ + +package com.stoutner.privacybrowser.asynctasks; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.graphics.Typeface; +import android.os.AsyncTask; +import android.os.Build; +import android.os.LocaleList; +import android.preference.PreferenceManager; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.StyleSpan; +import android.view.View; +import android.webkit.CookieManager; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.stoutner.privacybrowser.R; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Locale; + +// This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `SpannableStringBuilder[]` contains the results. +public class GetSource extends AsyncTask { + // Declare a weak reference to the calling activity. + private WeakReference activityWeakReference; + + // Store the user agent. + private String userAgent; + + public GetSource(Activity activity, String userAgent) { + // Populate the weak reference to the calling activity. + activityWeakReference = new WeakReference<>(activity); + + // Store the user agent. + this.userAgent = userAgent; + } + + // `onPreExecute()` operates on the UI thread. + @Override + protected void onPreExecute() { + // Get a handle for the activity. + Activity viewSourceActivity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((viewSourceActivity == null) || viewSourceActivity.isFinishing()) { + return; + } + + // Get a handle for the progress bar. + ProgressBar progressBar = viewSourceActivity.findViewById(R.id.progress_bar); + + // Make the progress bar visible. + progressBar.setVisibility(View.VISIBLE); + + // Set the progress bar to be indeterminate. + progressBar.setIndeterminate(true); + } + + @Override + protected SpannableStringBuilder[] doInBackground(String... formattedUrlString) { + // Initialize the response body String. + SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder(); + SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder(); + SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder(); + SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder(); + + // Get a handle for the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity == null) || activity.isFinishing()) { + return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder}; + } + + // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`. + try { + // Get the current URL from the main activity. + URL url = new URL(formattedUrlString[0]); + + // Open a connection to the URL. No data is actually sent at this point. + HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(); + + // Instantiate the variables necessary to build the request headers. + requestHeadersBuilder = new SpannableStringBuilder(); + int oldRequestHeadersBuilderLength; + int newRequestHeadersBuilderLength; + + + // Set the `Host` header property. + httpUrlConnection.setRequestProperty("Host", url.getHost()); + + // Add the `Host` header to the string builder and format the text. + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.append("Host"); + newRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + requestHeadersBuilder.append(": "); + requestHeadersBuilder.append(url.getHost()); + + + // Set the `Connection` header property. + httpUrlConnection.setRequestProperty("Connection", "keep-alive"); + + // Add the `Connection` header to the string builder and format the text. + requestHeadersBuilder.append(System.getProperty("line.separator")); + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.append("Connection"); + newRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + requestHeadersBuilder.append(": keep-alive"); + + + // Set the `User-Agent` header property. + httpUrlConnection.setRequestProperty("User-Agent", userAgent); + + // Add the `User-Agent` header to the string builder and format the text. + requestHeadersBuilder.append(System.getProperty("line.separator")); + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.append("User-Agent"); + newRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + requestHeadersBuilder.append(": "); + requestHeadersBuilder.append(userAgent); + + + // Set the `Upgrade-Insecure-Requests` header property. + httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1"); + + // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text. + requestHeadersBuilder.append(System.getProperty("line.separator")); + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.append("Upgrade-Insecure_Requests"); + newRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + requestHeadersBuilder.append(": 1"); + + + // Set the `x-requested-with` header property. + httpUrlConnection.setRequestProperty("x-requested-with", ""); + + // Add the `x-requested-with` header to the string builder and format the text. + requestHeadersBuilder.append(System.getProperty("line.separator")); + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.append("x-requested-with"); + newRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + requestHeadersBuilder.append(": "); + + + // Get a handle for the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); + + // Only populate `Do Not Track` if it is enabled. + if (sharedPreferences.getBoolean("do_not_track", false)) { + // Set the `dnt` header property. + httpUrlConnection.setRequestProperty("dnt", "1"); + + // Add the `dnt` header to the string builder and format the text. + requestHeadersBuilder.append(System.getProperty("line.separator")); + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + requestHeadersBuilder.append("dnt", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.append("dnt"); + newRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + requestHeadersBuilder.append(": 1"); + } + + + // Set the `Accept` header property. + httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"); + + // Add the `Accept` header to the string builder and format the text. + requestHeadersBuilder.append(System.getProperty("line.separator")); + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.append("Accept"); + newRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + requestHeadersBuilder.append(": "); + requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"); + + + // Instantiate a locale string. + String localeString; + + // Populate the locale string. + if (Build.VERSION.SDK_INT >= 24) { // SDK >= 24 has a list of locales. + // Get the list of locales. + LocaleList localeList = activity.getResources().getConfiguration().getLocales(); + + // Initialize a string builder to extract the locales from the list. + StringBuilder localesStringBuilder = new StringBuilder(); + + // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages. + int q = 10; + + // Populate the string builder with the contents of the locales list. + for (int i = 0; i < localeList.size(); i++) { + // Append a comma if there is already an item in the string builder. + if (i > 0) { + localesStringBuilder.append(","); + } + + // Get the indicated locale from the list. + localesStringBuilder.append(localeList.get(i)); + + // If not the first locale, append `;q=0.i`, which drops by .1 for each removal from the main locale. + if (q < 10) { + localesStringBuilder.append(";q=0."); + localesStringBuilder.append(q); + } + + // Decrement `q`. + q--; + } + + // Store the populated string builder in the locale string. + localeString = localesStringBuilder.toString(); + } else { // SDK < 24 only has a primary locale. + // Store the locale in the locale string. + localeString = Locale.getDefault().toString(); + } + + // Set the `Accept-Language` header property. + httpUrlConnection.setRequestProperty("Accept-Language", localeString); + + // Add the `Accept-Language` header to the string builder and format the text. + requestHeadersBuilder.append(System.getProperty("line.separator")); + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.append("Accept-Language"); + newRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + requestHeadersBuilder.append(": "); + requestHeadersBuilder.append(localeString); + + + // Get the cookies for the current domain. + String cookiesString = CookieManager.getInstance().getCookie(url.toString()); + + // Only process the cookies if they are not null. + if (cookiesString != null) { + // Set the `Cookie` header property. + httpUrlConnection.setRequestProperty("Cookie", cookiesString); + + // Add the `Cookie` header to the string builder and format the text. + requestHeadersBuilder.append(System.getProperty("line.separator")); + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.append("Cookie"); + newRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + requestHeadersBuilder.append(": "); + requestHeadersBuilder.append(cookiesString); + } + + + // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default. If the property is manually set, than `HttpUrlConnection` does not process the decoding. + // Add the `Accept-Encoding` header to the string builder and format the text. + requestHeadersBuilder.append(System.getProperty("line.separator")); + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + oldRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.append("Accept-Encoding"); + newRequestHeadersBuilderLength = requestHeadersBuilder.length(); + requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + requestHeadersBuilder.append(": gzip"); + + + // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block. + try { + // Initialize the string builders. + responseMessageBuilder = new SpannableStringBuilder(); + responseHeadersBuilder = new SpannableStringBuilder(); + + // Get the response code, which causes the connection to the server to be made. + int responseCode = httpUrlConnection.getResponseCode(); + + // Populate the response message string builder. + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + responseMessageBuilder.append(String.valueOf(responseCode)); + int newLength = responseMessageBuilder.length(); + responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + responseMessageBuilder.append(": "); + responseMessageBuilder.append(httpUrlConnection.getResponseMessage()); + + // Initialize the iteration variable. + int i = 0; + + // Iterate through the received header fields. + while (httpUrlConnection.getHeaderField(i) != null) { + // Add a new line if there is already information in the string builder. + if (i > 0) { + responseHeadersBuilder.append(System.getProperty("line.separator")); + } + + // Add the header to the string builder and format the text. + if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart. + responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { // Older versions not so much. + int oldLength = responseHeadersBuilder.length(); + responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i)); + int newLength = responseHeadersBuilder.length(); + responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength + 1, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + responseHeadersBuilder.append(": "); + responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i)); + + // Increment the iteration variable. + i++; + } + + // Instantiate an input stream for the response body. + InputStream inputStream; + + // Get the correct input stream based on the response code. + if (responseCode == 404) { // Get the error stream. + inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream()); + } else { // Get the response body stream. + inputStream = new BufferedInputStream(httpUrlConnection.getInputStream()); + } + + // Initialize the byte array output stream and the conversion buffer byte array. + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + byte[] conversionBufferByteArray = new byte[1024]; + + // Instantiate the variable to track the buffer length. + int bufferLength; + + try { + // Attempt to read data from the input stream and store it in the conversion buffer byte array. Also store the amount of data transferred in the buffer length variable. + while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) { // Proceed while the amount of data stored in the buffer is > 0. + // Write the contents of the conversion buffer to the byte array output stream. + byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength); + } + } catch (IOException e) { + e.printStackTrace(); + } + + // Close the input stream. + inputStream.close(); + + // Populate the response body string with the contents of the byte array output stream. + responseBodyBuilder.append(byteArrayOutputStream.toString()); + } finally { + // Disconnect `httpUrlConnection`. + httpUrlConnection.disconnect(); + } + } catch (IOException e) { + e.printStackTrace(); + } + + // Return the response body string as the result. + return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder}; + } + + // `onPostExecute()` operates on the UI thread. + @Override + protected void onPostExecute(SpannableStringBuilder[] viewSourceStringArray){ + // Get a handle for the activity. + Activity activity = activityWeakReference.get(); + + // Abort if the activity is gone. + if ((activity == null) || activity.isFinishing()) { + return; + } + + // Get handles for the text views. + TextView requestHeadersTextView = activity.findViewById(R.id.request_headers); + TextView responseMessageTextView = activity.findViewById(R.id.response_message); + TextView responseHeadersTextView = activity.findViewById(R.id.response_headers); + TextView responseBodyTextView = activity.findViewById(R.id.response_body); + ProgressBar progressBar = activity.findViewById(R.id.progress_bar); + SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.view_source_swiperefreshlayout); + + // Populate the text views. This can take a long time, and freeze the user interface, if the response body is particularly large. + requestHeadersTextView.setText(viewSourceStringArray[0]); + responseMessageTextView.setText(viewSourceStringArray[1]); + responseHeadersTextView.setText(viewSourceStringArray[2]); + responseBodyTextView.setText(viewSourceStringArray[3]); + + // Hide the progress bar. + progressBar.setIndeterminate(false); + progressBar.setVisibility(View.GONE); + + //Stop the swipe to refresh indicator if it is running + swipeRefreshLayout.setRefreshing(false); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.java b/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.java index c7e5ade3..b6708a40 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.java +++ b/app/src/main/java/com/stoutner/privacybrowser/views/NestedScrollWebView.java @@ -24,6 +24,7 @@ import android.util.AttributeSet; import android.view.MotionEvent; import android.webkit.WebView; +import androidx.annotation.NonNull; import androidx.core.view.NestedScrollingChild2; import androidx.core.view.NestedScrollingChildHelper; import androidx.core.view.ViewCompat; @@ -49,6 +50,9 @@ public class NestedScrollWebView extends WebView implements NestedScrollingChild private boolean domainSettingsApplied; private int domainSettingsDatabaseId; + // Keep track of when the domain name changes so that domain settings can be reapplied. This should never be null. + private String currentDomainName = ""; + // Track the resource requests. private ArrayList resourceRequests = new ArrayList<>(); private int blockedRequests; @@ -151,6 +155,23 @@ public class NestedScrollWebView extends WebView implements NestedScrollingChild } + // Current domain name. To function well when called, the domain name should never be allowed to be null. + public void setCurrentDomainName(@NonNull String domainName) { + // Store the current domain name. + currentDomainName = domainName; + } + + public void resetCurrentDomainName() { + // Reset the current domain name. + currentDomainName = ""; + } + + public String getCurrentDomainName() { + // Return the current domain name. + return currentDomainName; + } + + // Resource requests. public void addResourceRequest(String[] resourceRequest) { // Add the resource request to the list. -- 2.45.2