Handle content:// URLs in View Source. https://redmine.stoutner.com/issues/361
authorSoren Stoutner <soren@stoutner.com>
Mon, 10 May 2021 19:24:03 +0000 (12:24 -0700)
committerSoren Stoutner <soren@stoutner.com>
Mon, 10 May 2021 19:24:03 +0000 (12:24 -0700)
20 files changed:
.idea/codeStyles/Project.xml
.idea/compiler.xml
.idea/dictionaries/soren.xml
.idea/misc.xml
.idea/runConfigurations.xml
app/build.gradle
app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.kt
app/src/main/java/com/stoutner/privacybrowser/backgroundtasks/GetSourceBackgroundTask.java
app/src/main/java/com/stoutner/privacybrowser/fragments/AboutVersionFragment.java
app/src/main/java/com/stoutner/privacybrowser/viewmodelfactories/WebViewSourceFactory.kt
app/src/main/java/com/stoutner/privacybrowser/viewmodels/WebViewSource.kt
app/src/main/res/layout/view_source_coordinatorlayout.xml
app/src/main/res/values-de/strings.xml
app/src/main/res/values-es/strings.xml
app/src/main/res/values-fr/strings.xml
app/src/main/res/values-it/strings.xml
app/src/main/res/values-ru/strings.xml
app/src/main/res/values/strings.xml
build.gradle
gradle/wrapper/gradle-wrapper.properties

index de6c57105c8a1e908a1ed8ac97c607fb43db8bee..f5c1c8285bbfd4a10b99b9293dc2e74896c1a5dd 100644 (file)
       </option>
     </JavaCodeStyleSettings>
     <JetCodeStyleSettings>
-      <option name="PACKAGES_TO_USE_STAR_IMPORTS">
-        <value>
-          <package name="java.util" alias="false" withSubpackages="false" />
-          <package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
-          <package name="io.ktor" alias="false" withSubpackages="true" />
-        </value>
-      </option>
-      <option name="PACKAGES_IMPORT_LAYOUT">
-        <value>
-          <package name="" alias="false" withSubpackages="true" />
-          <package name="java" alias="false" withSubpackages="true" />
-          <package name="javax" alias="false" withSubpackages="true" />
-          <package name="kotlin" alias="false" withSubpackages="true" />
-          <package name="" alias="true" withSubpackages="true" />
-        </value>
-      </option>
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
     </JetCodeStyleSettings>
     <XML>
       <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
         </rules>
       </arrangement>
     </codeStyleSettings>
+    <codeStyleSettings language="kotlin">
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </codeStyleSettings>
   </code_scheme>
 </component>
\ No newline at end of file
index 8144c3cf0fbcacae7352023d9e35b97b739392dc..f86176500aa04d6864303ded0e011937dc6559ff 100644 (file)
@@ -11,6 +11,6 @@
       <entry name="!?*.kt" />
       <entry name="!?*.clj" />
     </wildcardResourcePatterns>
-    <bytecodeTargetLevel target="1.8" />
+    <bytecodeTargetLevel target="11" />
   </component>
 </project>
\ No newline at end of file
index c4e8f141f873489aa6e0bbee66ba9ab9a916bbe0..2dd77ccf13c8ed13a412c272c5f6f34bc231b2d3 100644 (file)
@@ -88,6 +88,7 @@
       <w>gitweb</w>
       <w>glitchy</w>
       <w>googleplay</w>
+      <w>grrrr</w>
       <w>homescreen</w>
       <w>hsts</w>
       <w>huawei</w>
index eccecb07688035cc51daac4eda671a4c5f566201..3114d958f51439b232c22c7b392d0f3d6748233f 100644 (file)
@@ -5,7 +5,7 @@
     <option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
     <option name="myNullables">
       <value>
-        <list size="10">
+        <list size="14">
           <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
           <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
           <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
           <item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
           <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
           <item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
+          <item index="10" class="java.lang.String" itemvalue="com.android.annotations.Nullable" />
+          <item index="11" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.Nullable" />
+          <item index="12" class="java.lang.String" itemvalue="io.reactivex.annotations.Nullable" />
+          <item index="13" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.Nullable" />
         </list>
       </value>
     </option>
     <option name="myNotNulls">
       <value>
-        <list size="9">
+        <list size="13">
           <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
           <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
           <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
           <item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
           <item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
           <item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
+          <item index="9" class="java.lang.String" itemvalue="com.android.annotations.NonNull" />
+          <item index="10" class="java.lang.String" itemvalue="org.eclipse.jdt.annotation.NonNull" />
+          <item index="11" class="java.lang.String" itemvalue="io.reactivex.annotations.NonNull" />
+          <item index="12" class="java.lang.String" itemvalue="io.reactivex.rxjava3.annotations.NonNull" />
         </list>
       </value>
     </option>
   </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">
index 7f68460d8b38ac04e3a3224d7c79ef719b1991a9..e497da999824b5c5629039f2e74794dd96b6c419 100644 (file)
@@ -3,6 +3,7 @@
   <component name="RunConfigurationProducerService">
     <option name="ignoredProducers">
       <set>
+        <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
         <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
         <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
         <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
index a49c12c35166462ecb98f7be314c7f189e46421b..02170b364a94c430dc8bdeb7b739324409f76205 100644 (file)
@@ -60,11 +60,6 @@ android {
             dimension "basic"
         }
     }
-
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
-    }
 }
 
 repositories {
@@ -88,11 +83,11 @@ dependencies {
     implementation 'androidx.webkit:webkit:1.4.0'
 
     // Include the Kotlin standard libraries.  This should be the same version number listed in project build.gradle.
-    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32'
+    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0'
 
     // Include the Google material library.
     implementation 'com.google.android.material:material:1.3.0'
 
     // Only compile AdMob ads for the free flavor.
-    freeImplementation 'com.google.android.gms:play-services-ads:20.0.0'
+    freeImplementation 'com.google.android.gms:play-services-ads:20.1.0'
 }
\ No newline at end of file
index 173313f65da654b540f2d88af163bc59ba176aa3..1df0515230041e988a88faddb294f2be2e77e987 100644 (file)
@@ -66,6 +66,14 @@ class ViewSourceActivity: AppCompatActivity() {
     private lateinit var finalGrayColorSpan: ForegroundColorSpan
     private lateinit var redColorSpan: ForegroundColorSpan
 
+    // Declare the class views.
+    private lateinit var requestHeadersTitleTextView: TextView
+    private lateinit var requestHeadersTextView: TextView
+    private lateinit var responseMessageTitleTextView: TextView
+    private lateinit var responseMessageTextView: TextView
+    private lateinit var responseHeadersTitleTextView: TextView
+    private lateinit var responseBodyTitleTextView: TextView
+
     override fun onCreate(savedInstanceState: Bundle?) {
         // Get a handle for the shared preferences.
         val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)
@@ -88,8 +96,8 @@ class ViewSourceActivity: AppCompatActivity() {
         val intent = intent
 
         // Get the information from the intent.
-        val currentUrl = intent.getStringExtra(CURRENT_URL)
-        val userAgent = intent.getStringExtra(USER_AGENT)
+        val currentUrl = intent.getStringExtra(CURRENT_URL)!!
+        val userAgent = intent.getStringExtra(USER_AGENT)!!
 
         // Set the content view.
         setContentView(R.layout.view_source_coordinatorlayout)
@@ -111,12 +119,16 @@ class ViewSourceActivity: AppCompatActivity() {
 
         // Get handles for the views.
         val urlEditText = findViewById<EditText>(R.id.url_edittext)
-        val requestHeadersTextView = findViewById<TextView>(R.id.request_headers)
-        val responseMessageTextView = findViewById<TextView>(R.id.response_message)
-        val responseHeadersTextView = findViewById<TextView>(R.id.response_headers)
-        val responseBodyTextView = findViewById<TextView>(R.id.response_body)
         val progressBar = findViewById<ProgressBar>(R.id.progress_bar)
         val swipeRefreshLayout = findViewById<SwipeRefreshLayout>(R.id.view_source_swiperefreshlayout)
+        requestHeadersTitleTextView = findViewById(R.id.request_headers_title_textview)
+        requestHeadersTextView = findViewById(R.id.request_headers_textview)
+        responseMessageTitleTextView = findViewById(R.id.response_message_title_textview)
+        responseMessageTextView = findViewById(R.id.response_message_textview)
+        responseHeadersTitleTextView = findViewById(R.id.response_headers_title_textivew)
+        val responseHeadersTextView = findViewById<TextView>(R.id.response_headers_textview)
+        responseBodyTitleTextView = findViewById(R.id.response_body_title_textview)
+        val responseBodyTextView = findViewById<TextView>(R.id.response_body_textview)
 
         // Populate the URL text box.
         urlEditText.setText(currentUrl)
@@ -253,8 +265,11 @@ class ViewSourceActivity: AppCompatActivity() {
         // Set the progress bar to be indeterminate.
         progressBar.isIndeterminate = true
 
+        // Update the layout.
+        updateLayout(currentUrl)
+
         // Instantiate the WebView source factory.
-        val webViewSourceFactory: ViewModelProvider.Factory = WebViewSourceFactory(currentUrl!!, userAgent!!, localeString, proxy, MainWebViewActivity.executorService)
+        val webViewSourceFactory: ViewModelProvider.Factory = WebViewSourceFactory(currentUrl, userAgent, localeString, proxy, contentResolver, MainWebViewActivity.executorService)
 
         // Instantiate the WebView source view model class.
         val webViewSource = ViewModelProvider(this, webViewSourceFactory).get(WebViewSource::class.java)
@@ -294,6 +309,9 @@ class ViewSourceActivity: AppCompatActivity() {
             // Get the URL.
             val urlString = urlEditText.text.toString()
 
+            // Update the layout.
+            updateLayout(urlString)
+
             // Get the updated source.
             webViewSource.updateSource(urlString)
         }
@@ -317,6 +335,9 @@ class ViewSourceActivity: AppCompatActivity() {
                 // Get the URL.
                 val urlString = urlEditText.text.toString()
 
+                // Update the layout.
+                updateLayout(urlString)
+
                 // Get the updated source.
                 webViewSource.updateSource(urlString)
 
@@ -411,4 +432,28 @@ class ViewSourceActivity: AppCompatActivity() {
             }
         }
     }
+
+    private fun updateLayout(urlString: String) {
+        if (urlString.startsWith("content://")) {  // This is a content URL.
+            // Hide the unused text views.
+            requestHeadersTitleTextView.visibility = View.GONE
+            requestHeadersTextView.visibility = View.GONE
+            responseMessageTitleTextView.visibility = View.GONE
+            responseMessageTextView.visibility = View.GONE
+
+            // Change the text of the remaining title text views.
+            responseHeadersTitleTextView.setText(R.string.content_metadata)
+            responseBodyTitleTextView.setText(R.string.content_data)
+        } else {  // This is not a content URL.
+            // Show the views.
+            requestHeadersTitleTextView.visibility = View.VISIBLE
+            requestHeadersTextView.visibility = View.VISIBLE
+            responseMessageTitleTextView.visibility = View.VISIBLE
+            responseMessageTextView.visibility = View.VISIBLE
+
+            // Restore the text of the other title text views.
+            responseHeadersTitleTextView.setText(R.string.response_headers)
+            responseBodyTitleTextView.setText(R.string.response_body)
+        }
+    }
 }
\ No newline at end of file
index 3ecac1d6ff3d4c174a94ff67264252a34936d85a..a79e6e74d7a5fed52ff04601d3b3e09aed9b71cb 100644 (file)
 
 package com.stoutner.privacybrowser.backgroundtasks;
 
+import android.content.ContentResolver;
+import android.database.Cursor;
 import android.graphics.Typeface;
+import android.net.Uri;
 import android.os.Build;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
@@ -29,321 +32,377 @@ import android.webkit.CookieManager;
 import com.stoutner.privacybrowser.viewmodels.WebViewSource;
 
 import java.io.BufferedInputStream;
+import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.Proxy;
 import java.net.URL;
 
 public class GetSourceBackgroundTask {
-    public SpannableStringBuilder[] acquire(String urlString, String userAgent, String localeString, Proxy proxy, WebViewSource webViewSource) {
+    public SpannableStringBuilder[] acquire(String urlString, String userAgent, String localeString, Proxy proxy, ContentResolver contentResolver, WebViewSource webViewSource) {
         // Initialize the spannable string builders.
         SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
         SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
         SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
         SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
 
-        // 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(urlString);
+        if (urlString.startsWith("content://")) {  // This is a content URL.
+            // Attempt to read the content data.  Return an error if this fails.
+            try {
+                // Get a URI for the content URL.
+                Uri contentUri = Uri.parse(urlString);
 
-            // Open a connection to the URL.  No data is actually sent at this point.
-            HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
+                // Define the variables necessary to build the response headers.
+                int oldResponseHeadersBuilderLength;
+                int newResponseHeadersBuilderLength;
 
-            // Define the variables necessary to build the request headers.
-            requestHeadersBuilder = new SpannableStringBuilder();
-            int oldRequestHeadersBuilderLength;
-            int newRequestHeadersBuilderLength;
+                // Get a cursor with metadata about the content URL.
+                Cursor contentCursor = contentResolver.query(contentUri, null, null, null, null);
 
+                // Move the content cursor to the first row.
+                contentCursor.moveToFirst();
 
-            // Set the `Host` header property.
-            httpUrlConnection.setRequestProperty("Host", url.getHost());
+                for (int i = 0; i < contentCursor.getColumnCount(); i++) {
+                    // Add a new line if this is not the first entry.
+                    if (i > 0) {
+                        responseHeadersBuilder.append(System.getProperty("line.separator"));
+                    }
 
-            // 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, 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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  keep-alive");
+                    // Add each header to the string builder.
+                    if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                        responseHeadersBuilder.append(contentCursor.getColumnName(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    } else {  // Older versions are not so much.
+                        oldResponseHeadersBuilderLength = responseHeadersBuilder.length();
+                        responseHeadersBuilder.append(contentCursor.getColumnName(i));
+                        newResponseHeadersBuilderLength = responseHeadersBuilder.length();
+                        responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldResponseHeadersBuilderLength, newResponseHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                    responseHeadersBuilder.append(":  ");
+                    responseHeadersBuilder.append(contentCursor.getString(i));
+                }
 
+                // Close the content cursor.
+                contentCursor.close();
 
-            // Set the `Upgrade-Insecure-Requests` header property.
-            httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
+                // Create a buffered string reader for the content data.
+                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(contentResolver.openInputStream(contentUri)));
 
-            // 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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                // Get the data from the buffered reader one line at a time.
+                for (String contentLineString; ((contentLineString = bufferedReader.readLine()) != null);) {
+                    // Add the line to the response body builder.
+                    responseBodyBuilder.append(contentLineString);
+
+                    // Append a new line.
+                    responseBodyBuilder.append("\n");
+                }
+            } catch (Exception exception) {
+                // Return the error message.
+                webViewSource.returnError(exception.toString());
             }
-            requestHeadersBuilder.append(":  1");
+        } else {  // This is not a content URL.
+            // 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(urlString);
 
+                // Open a connection to the URL.  No data is actually sent at this point.
+                HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
 
-            // Set the `User-Agent` header property.
-            httpUrlConnection.setRequestProperty("User-Agent", userAgent);
+                // Define the variables necessary to build the request headers.
+                int oldRequestHeadersBuilderLength;
+                int newRequestHeadersBuilderLength;
 
-            // 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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  ");
-            requestHeadersBuilder.append(userAgent);
-
-
-            // 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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  ");
 
+                // Set the `Host` header property.
+                httpUrlConnection.setRequestProperty("Host", url.getHost());
 
-            // Set the `Sec-Fetch-Site` header property.
-            httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
+                // 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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                requestHeadersBuilder.append(":  ");
+                requestHeadersBuilder.append(url.getHost());
 
-            // Add the `Sec-Fetch-Site` 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("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Sec-Fetch-Site");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  none");
 
+                // 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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                requestHeadersBuilder.append(":  keep-alive");
 
-            // Set the `Sec-Fetch-Mode` header property.
-            httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
 
-            // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Sec-Fetch-Mode");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  navigate");
+                // 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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                requestHeadersBuilder.append(":  1");
 
-            // Set the `Sec-Fetch-User` header property.
-            httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
 
-            // Add the `Sec-Fetch-User` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
-                requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            } else {  // Older versions not so much.
-                oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.append("Sec-Fetch-User");
-                newRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  ?1");
+                // Set the `User-Agent` header property.
+                httpUrlConnection.setRequestProperty("User-Agent", userAgent);
 
+                // Add the `User-Agent` header to the string builder and format the text.
+                requestHeadersBuilder.append(System.getProperty("line.separator"));
+                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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                requestHeadersBuilder.append(":  ");
+                requestHeadersBuilder.append(userAgent);
 
-            // Set the `Accept` header property.
-            httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
 
-            // Add the `Accept` header to the string builder and format the text.
-            requestHeadersBuilder.append(System.getProperty("line.separator"));
-            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, 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,application/signed-exchange;v=b3");
-
-
-            // 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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  ");
-            requestHeadersBuilder.append(localeString);
+                // 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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                requestHeadersBuilder.append(":  ");
 
-            // 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) {
-                // Add the cookies to the header property.
-                httpUrlConnection.setRequestProperty("Cookie", cookiesString);
+                // Set the `Sec-Fetch-Site` header property.
+                httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
 
-                // Add the cookie header to the string builder and format the text.
+                // Add the `Sec-Fetch-Site` 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);
+                    requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                 } else {  // Older versions not so much.
                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
-                    requestHeadersBuilder.append("Cookie");
+                    requestHeadersBuilder.append("Sec-Fetch-Site");
                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                 }
-                requestHeadersBuilder.append(":  ");
-                requestHeadersBuilder.append(cookiesString);
-            }
+                requestHeadersBuilder.append(":  none");
 
 
-            // `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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-            }
-            requestHeadersBuilder.append(":  gzip");
+                // Set the `Sec-Fetch-Mode` header property.
+                httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
 
+                // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
+                requestHeadersBuilder.append(System.getProperty("line.separator"));
+                if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
+                    requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                } else {  // Older versions not so much.
+                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.append("Sec-Fetch-Mode");
+                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                requestHeadersBuilder.append(":  navigate");
 
-            // 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();
+                // Set the `Sec-Fetch-User` header property.
+                httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
 
-                // Populate the response message string builder.
+                // Add the `Sec-Fetch-User` 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.
-                    responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    requestHeadersBuilder.append("Sec-Fetch-User", 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);
+                    oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.append("Sec-Fetch-User");
+                    newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                    requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                 }
-                responseMessageBuilder.append(":  ");
-                responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
+                requestHeadersBuilder.append(":  ?1");
 
-                // 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"));
-                    }
+                // Set the `Accept` header property.
+                httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
+
+                // Add the `Accept` header to the string builder and format the text.
+                requestHeadersBuilder.append(System.getProperty("line.separator"));
+                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, 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,application/signed-exchange;v=b3");
+
+
+                // 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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+                requestHeadersBuilder.append(":  ");
+                requestHeadersBuilder.append(localeString);
+
+
+                // Get the cookies for the current domain.
+                String cookiesString = CookieManager.getInstance().getCookie(url.toString());
 
-                    // Add the header to the string builder and format the text.
+                // Only process the cookies if they are not null.
+                if (cookiesString != null) {
+                    // Add the cookies to the 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.
-                        responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                        requestHeadersBuilder.append("Cookie", 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, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                        oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                        requestHeadersBuilder.append("Cookie");
+                        newRequestHeadersBuilderLength = requestHeadersBuilder.length();
+                        requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                     }
-                    responseHeadersBuilder.append(":  ");
-                    responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
-
-                    // Increment the iteration variable.
-                    i++;
+                    requestHeadersBuilder.append(":  ");
+                    requestHeadersBuilder.append(cookiesString);
                 }
 
-                // 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());
+                // `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, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                 }
+                requestHeadersBuilder.append(":  gzip");
 
-                // Initialize the byte array output stream and the conversion buffer byte array.
-                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-                byte[] conversionBufferByteArray = new byte[1024];
-
-                // Define the buffer length variable.
-                int bufferLength;
 
+                // 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 {
-                    // Attempt to read data from the input stream and store it in the conversion buffer byte array.  Also store the amount of data read 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);
+                    // 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);
                     }
-                } catch (IOException exception) {
-                    // Return the error message.
-                    webViewSource.returnError(exception.toString());
-                }
+                    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, 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;
 
-                // Close the input stream.
-                inputStream.close();
+                    // 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());
+                    }
 
-                // Populate the response body string with the contents of the byte array output stream.
-                responseBodyBuilder.append(byteArrayOutputStream.toString());
-            } finally {
-                // Disconnect HTTP URL connection.
-                httpUrlConnection.disconnect();
+                    // Initialize the byte array output stream and the conversion buffer byte array.
+                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+                    byte[] conversionBufferByteArray = new byte[1024];
+
+                    // Define the buffer length variable.
+                    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 read 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 exception) {
+                        // Return the error message.
+                        webViewSource.returnError(exception.toString());
+                    }
+
+                    // 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 HTTP URL connection.
+                    httpUrlConnection.disconnect();
+                }
+            } catch (Exception exception) {
+                // Return the error message.
+                webViewSource.returnError(exception.toString());
             }
-        } catch (Exception exception) {
-            // Return the error message.
-            webViewSource.returnError(exception.toString());
         }
 
         // Return the response body string as the result.
index 18062c24230176ba9aa2b7d6f676fc2066b9809c..9436110fef7454a1019775e5d11f38e261c8ff47 100644 (file)
@@ -356,6 +356,7 @@ public class AboutVersionFragment extends Fragment {
         ultraPrivacyTextView.setText(ultraPrivacyStringBuilder);
 
         // Only populate the radio text view if there is a radio in the device.
+        // Null must be checked because some Samsung tablets report a null value for the radio instead of an empty string.  Grrrr.  <https://redmine.stoutner.com/issues/701>
         if ((radio != null) && !radio.isEmpty()) {
             String radioLabel = getString(R.string.radio) + "  ";
             SpannableStringBuilder radioStringBuilder = new SpannableStringBuilder(radioLabel + radio);
index 870e784e638cbdeddef5106d6d50481bbe86ca51..e44e74d81847d2d2be16f4741249459fbd9c5751 100644 (file)
 
 package com.stoutner.privacybrowser.viewmodelfactories
 
+import android.content.ContentResolver
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 
 import java.net.Proxy
 import java.util.concurrent.ExecutorService
 
-class WebViewSourceFactory (private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy,
+class WebViewSourceFactory (private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy, private val contentResolver: ContentResolver,
                             private val executorService: ExecutorService): ViewModelProvider.Factory {
     // Override the create function in order to add the provided arguments.
     override fun <T: ViewModel?> create(modelClass: Class<T>): T {
         // Return a new instance of the model class with the provided arguments.
-        return modelClass.getConstructor(String::class.java, String::class.java, Boolean::class.java, String::class.java, Proxy::class.java, ExecutorService::class.java)
-                .newInstance(urlString, userAgent, localeString, proxy, executorService)
+        return modelClass.getConstructor(String::class.java, String::class.java, String::class.java, Proxy::class.java, ContentResolver::class.java, ExecutorService::class.java)
+                .newInstance(urlString, userAgent, localeString, proxy, contentResolver, executorService)
     }
 }
\ No newline at end of file
index 6d650b0cb8f0a48069970f86d729bebd4b47296c..1b9d9e1203dd15399c1efba4f05e5e6337115c13 100644 (file)
@@ -19,6 +19,7 @@
 
 package com.stoutner.privacybrowser.viewmodels
 
+import android.content.ContentResolver
 import android.text.SpannableStringBuilder
 
 import androidx.lifecycle.LiveData
@@ -30,7 +31,7 @@ import com.stoutner.privacybrowser.backgroundtasks.GetSourceBackgroundTask
 import java.net.Proxy
 import java.util.concurrent.ExecutorService
 
-class WebViewSource(private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy,
+class WebViewSource(private val urlString: String, private val userAgent: String, private val localeString: String, private val proxy: Proxy, private val contentResolver: ContentResolver,
                     private val executorService: ExecutorService): ViewModel() {
     // Initialize the mutable live data variables.
     private val mutableLiveDataSourceStringArray = MutableLiveData<Array<SpannableStringBuilder>>()
@@ -42,7 +43,7 @@ class WebViewSource(private val urlString: String, private val userAgent: String
         val getSourceBackgroundTask = GetSourceBackgroundTask()
 
         // Get the source.
-        executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, localeString, proxy, this)) }
+        executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, localeString, proxy, contentResolver, this)) }
     }
 
     // The source observer.
@@ -72,6 +73,6 @@ class WebViewSource(private val urlString: String, private val userAgent: String
         val getSourceBackgroundTask = GetSourceBackgroundTask()
 
         // Get the source.
-        executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, localeString, proxy, this)) }
+        executorService.execute { mutableLiveDataSourceStringArray.postValue(getSourceBackgroundTask.acquire(urlString, userAgent, localeString, proxy, contentResolver, this)) }
     }
 }
\ No newline at end of file
index f8c1c8b6b01d53b4dd1d1ce03fca03b39d0c735e..5fd4566ce733ab8ed9f42d16f6821be389aff7a1 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
+  Copyright © 2017-2021 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
@@ -84,6 +84,7 @@
 
                     <!-- Request headers. -->
                     <TextView
+                        android:id="@+id/request_headers_title_textview"
                         android:layout_height="wrap_content"
                         android:layout_width="match_parent"
                         android:text="@string/request_headers"
@@ -93,7 +94,7 @@
                         android:textStyle="bold" />
 
                     <TextView
-                        android:id="@+id/request_headers"
+                        android:id="@+id/request_headers_textview"
                         android:layout_height="wrap_content"
                         android:layout_width="match_parent"
                         android:textIsSelectable="true"
 
                     <!-- Response message. -->
                     <TextView
+                        android:id="@+id/response_message_title_textview"
                         android:layout_height="wrap_content"
                         android:layout_width="match_parent"
                         android:text="@string/response_message"
                         android:textStyle="bold" />
 
                     <TextView
-                        android:id="@+id/response_message"
+                        android:id="@+id/response_message_textview"
                         android:layout_height="wrap_content"
                         android:layout_width="match_parent"
                         android:textIsSelectable="true"
                         android:layout_marginBottom="8dp" />
 
                     <!-- Response headers. -->
+                    <!-- The title text is set programatically. -->
                     <TextView
+                        android:id="@+id/response_headers_title_textivew"
                         android:layout_height="wrap_content"
                         android:layout_width="match_parent"
-                        android:text="@string/response_headers"
                         android:textAlignment="center"
                         android:textSize="18sp"
                         android:textColor="?attr/blueTextColor"
                         android:textStyle="bold" />
 
                     <TextView
-                        android:id="@+id/response_headers"
+                        android:id="@+id/response_headers_textview"
                         android:layout_height="wrap_content"
                         android:layout_width="match_parent"
                         android:textIsSelectable="true"
                         android:layout_marginBottom="8dp" />
 
                     <!-- Response body. -->
+                    <!-- The title text is set programatically. -->
                     <TextView
+                        android:id="@+id/response_body_title_textview"
                         android:layout_height="wrap_content"
                         android:layout_width="match_parent"
-                        android:text="@string/response_body"
                         android:textAlignment="center"
                         android:textSize="18sp"
                         android:textColor="?attr/blueTextColor"
                         android:textStyle="bold" />
 
                     <TextView
-                        android:id="@+id/response_body"
+                        android:id="@+id/response_body_textview"
                         android:layout_height="wrap_content"
                         android:layout_width="match_parent"
                         android:textIsSelectable="true" />
index 11f3539c14230946078c16dfa97b27864c107709..d31e741cba3d83e0e3683fc2020aed5b83c2fb0e 100644 (file)
         <string name="open_intents_in_new_tab_summary">Intents sind Links, die von anderen Apps übergeben werden.</string>
         <string name="swipe_to_refresh">Herunterziehen zum Aktualisieren</string>
         <string name="swipe_to_refresh_summary">Einige Websites funktionieren nicht, wenn "Herunterziehen zum Aktualisieren" eingeschaltet ist.</string>
+        <string name="download_with_external_app">Mit einer externen App herunterladen</string>
+        <string name="download_with_external_app_summary">Eine externe App verwenden, um Dateien herunterzuladen.</string>
         <string name="scroll_app_bar">App-Leiste scrollen</string>
         <string name="scroll_app_bar_summary">Scrollt die App-Leiste mit der URL nach oben, wenn die Webseite gescrollt wird.</string>
         <string name="display_additional_app_bar_icons">Weitere Icons in der Titelleiste</string>
index 8ad3703d03797f8be6073ac35a02c8913f202d10..5d93c0a982e536eaf21341ea9a1559d2b845c138 100644 (file)
         <string name="open_intents_in_new_tab_summary">Los contenidos son enlaces enviados desde otras apps.</string>
         <string name="swipe_to_refresh">Deslizar para actualizar</string>
         <string name="swipe_to_refresh_summary">Algunas webs no funcionan bien si la opción deslizar para actualizar está habilitada.</string>
+        <string name="download_with_external_app">Descargar con una app externa</string>
+        <string name="download_with_external_app_summary">Use una app externa para descargar archivos.</string>
         <string name="scroll_app_bar">Desplazar la barra de aplicaciones</string>
         <string name="scroll_app_bar_summary">Desplazar la barra de aplicaciones desde la parte superior de la pantalla cuando el WebView se desplaza hacia abajo.</string>
         <string name="display_additional_app_bar_icons">Mostrar iconos adicionales en la barra de aplicación</string>
index 5590d1d730723baba681d8cc00c4fb059198b1b7..7c0ae131884e8a4aa6e70ccd17e89f5022202506 100644 (file)
     <!-- Bookmarks. -->
     <string name="bookmarks">Favoris</string>
     <string name="database_view">Voir base de données</string>
+    <string name="bookmark_opened_in_background">Le signet a été ouvert dans un onglet en arrière-plan.</string>
     <string name="create_bookmark">Créer un favori</string>
     <string name="create_folder">Créer un dossier</string>
     <string name="current_bookmark_icon">Icone actuelle</string>
         <string name="open_intents_in_new_tab_summary">Les intentions sont des liens envoyés à partir d\'autres applications.</string>
         <string name="swipe_to_refresh">Glisser pour rafraîchir</string>
         <string name="swipe_to_refresh_summary">Certains sites Web ne fonctionnent pas bien "Glissez pour refraîchir" est activé.</string>
+        <string name="download_with_external_app">Téléchargement avec une app externe</string>
+        <string name="download_with_external_app_summary">Utiliser une application externe pour télécharger des fichiers.</string>
         <string name="scroll_app_bar">Défilement barre d\'applications</string>
         <string name="scroll_app_bar_summary">Faites défiler la barre d\'applications en haut de l\'écran lorsque WebView défile vers le bas.</string>
         <string name="display_additional_app_bar_icons">Icônes supplémentaires dans la barre d\'applications</string>
index 0cea74196e0f6fcd8c5da8dcfbf76187ac910b7c..c60d40466b6f0a66a8d0e2cbdd109086408b9fd2 100644 (file)
         <string name="open_intents_in_new_tab_summary">Gli intenti sono link inviati da altre app.</string>
         <string name="swipe_to_refresh">Swipe per aggiornare</string>
         <string name="swipe_to_refresh_summary">Alcuni siti non funzionano correttamente se questa opzione è abilitata.</string>
+        <string name="download_with_external_app">Scarica con una applicazione esterna</string>
+        <string name="download_with_external_app_summary">Utilizza una applicazione esterna per scaricare i file.</string>
         <string name="scroll_app_bar">Permetti lo scrolling della barra dell\'applicazione</string>
         <string name="scroll_app_bar_summary">Permette lo scorrere della barra dell\'applicazione dalla parte alta dello schermo quando si effettua lo scrolling.</string>
         <string name="display_additional_app_bar_icons">Mostra icone addizionali nella barra dell\'applicazione</string>
index 5ece332cca14094e8076630491bf3eba488a9156..c77b79a8e5b8a310c410636ff2c585f45ccaeabf 100644 (file)
         <string name="open_intents_in_new_tab_summary">Цели - это ссылки, отправленные из других приложений.</string>
         <string name="swipe_to_refresh">Потянуть для обновления</string>
         <string name="swipe_to_refresh_summary">Некоторые веб-сайты могут работать некорректно при включении данной опции.</string>
+        <string name="download_with_external_app">Загрузка во внешнем приложении</string>
+        <string name="download_with_external_app_summary">Использовать внешнее приложение для загрузки файлов.</string>
         <string name="scroll_app_bar">Прокручивать панель приложения</string>
         <string name="scroll_app_bar_summary">Прокручивает панель приложения вверху экрана при прокрутке WebView вниз.</string>
         <string name="display_additional_app_bar_icons">Отображать дополнительные значки на панели приложения</string>
index f45b434d7eb83854126adb2be9ae9e341373a329..45a352d6eca0f0f208eb32f4fde6bb459e1f70f6 100644 (file)
     <string name="response_message">Response Message</string>
     <string name="response_headers">Response Headers</string>
     <string name="response_body">Response Body</string>
+    <string name="content_metadata">Content Metadata</string>
+    <string name="content_data">Content Data</string>
     <string name="about_view_source">About View Source</string>
     <string name="about_view_source_message">Because Android’s WebView does not expose the source information,
         a separate request was made using system tools to gather the information displayed in this activity.
index b07a40b20ba074060a0dba0cb44ad9fa3a141ffe..01f38d5cda3563c48b9e3d598766f900bdd335f1 100644 (file)
 
 buildscript {
     repositories {
-        jcenter()
         google()
+        mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.1.3'
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32"
+        classpath 'com.android.tools.build:gradle:4.2.0'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.0"
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
@@ -35,7 +35,7 @@ buildscript {
 
 allprojects {
     repositories {
-        jcenter()
         google()
+        mavenCentral()
     }
 }
\ No newline at end of file
index 4a958fdf453e4bf49f258dd9e9c4e2f5ebf3a817..85d1a808ab0f1623fd1d02cc673c4a1c1cf5fb54 100644 (file)
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip