Add the Sec-Fetch headers to View Source. https://redmine.stoutner.com/issues/503
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / asynctasks / GetSource.java
1 /*
2  * Copyright © 2017-2019 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.asynctasks;
21
22 import android.app.Activity;
23 import android.content.SharedPreferences;
24 import android.graphics.Typeface;
25 import android.os.AsyncTask;
26 import android.os.Build;
27 import android.os.LocaleList;
28 import android.preference.PreferenceManager;
29 import android.text.SpannableStringBuilder;
30 import android.text.Spanned;
31 import android.text.style.StyleSpan;
32 import android.view.View;
33 import android.webkit.CookieManager;
34 import android.widget.ProgressBar;
35 import android.widget.TextView;
36
37 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
38
39 import com.stoutner.privacybrowser.R;
40
41 import java.io.BufferedInputStream;
42 import java.io.ByteArrayOutputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.lang.ref.WeakReference;
46 import java.net.HttpURLConnection;
47 import java.net.URL;
48 import java.util.Locale;
49
50 // This must run asynchronously because it involves a network request.  `String` declares the parameters.  `Void` does not declare progress units.  `SpannableStringBuilder[]` contains the results.
51 public class GetSource extends AsyncTask<String, Void, SpannableStringBuilder[]> {
52     // Declare a weak reference to the calling activity.
53     private WeakReference<Activity> activityWeakReference;
54
55     // Store the user agent.
56     private String userAgent;
57
58     public GetSource(Activity activity, String userAgent) {
59         // Populate the weak reference to the calling activity.
60         activityWeakReference = new WeakReference<>(activity);
61
62         // Store the user agent.
63         this.userAgent = userAgent;
64     }
65
66     // `onPreExecute()` operates on the UI thread.
67     @Override
68     protected void onPreExecute() {
69         // Get a handle for the activity.
70         Activity viewSourceActivity = activityWeakReference.get();
71
72         // Abort if the activity is gone.
73         if ((viewSourceActivity == null) || viewSourceActivity.isFinishing()) {
74             return;
75         }
76
77         // Get a handle for the progress bar.
78         ProgressBar progressBar = viewSourceActivity.findViewById(R.id.progress_bar);
79
80         // Make the progress bar visible.
81         progressBar.setVisibility(View.VISIBLE);
82
83         // Set the progress bar to be indeterminate.
84         progressBar.setIndeterminate(true);
85     }
86
87     @Override
88     protected SpannableStringBuilder[] doInBackground(String... formattedUrlString) {
89         // Initialize the response body String.
90         SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
91         SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
92         SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
93         SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
94
95         // Get a handle for the activity.
96         Activity activity = activityWeakReference.get();
97
98         // Abort if the activity is gone.
99         if ((activity == null) || activity.isFinishing()) {
100             return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
101         }
102
103         // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
104         try {
105             // Get the current URL from the main activity.
106             URL url = new URL(formattedUrlString[0]);
107
108             // Open a connection to the URL.  No data is actually sent at this point.
109             HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();
110
111             // Define the variables necessary to build the request headers.
112             requestHeadersBuilder = new SpannableStringBuilder();
113             int oldRequestHeadersBuilderLength;
114             int newRequestHeadersBuilderLength;
115
116
117             // Set the `Host` header property.
118             httpUrlConnection.setRequestProperty("Host", url.getHost());
119
120             // Add the `Host` header to the string builder and format the text.
121             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
122                 requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
123             } else {  // Older versions not so much.
124                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
125                 requestHeadersBuilder.append("Host");
126                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
127                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
128             }
129             requestHeadersBuilder.append(":  ");
130             requestHeadersBuilder.append(url.getHost());
131
132
133             // Set the `Connection` header property.
134             httpUrlConnection.setRequestProperty("Connection", "keep-alive");
135
136             // Add the `Connection` header to the string builder and format the text.
137             requestHeadersBuilder.append(System.getProperty("line.separator"));
138             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
139                 requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
140             } else {  // Older versions not so much.
141                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
142                 requestHeadersBuilder.append("Connection");
143                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
144                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
145             }
146             requestHeadersBuilder.append(":  keep-alive");
147
148
149             // Set the `Upgrade-Insecure-Requests` header property.
150             httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
151
152             // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
153             requestHeadersBuilder.append(System.getProperty("line.separator"));
154             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
155                 requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
156             } else {  // Older versions not so much.
157                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
158                 requestHeadersBuilder.append("Upgrade-Insecure_Requests");
159                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
160                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
161             }
162             requestHeadersBuilder.append(":  1");
163
164
165             // Set the `User-Agent` header property.
166             httpUrlConnection.setRequestProperty("User-Agent", userAgent);
167
168             // Add the `User-Agent` header to the string builder and format the text.
169             requestHeadersBuilder.append(System.getProperty("line.separator"));
170             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
171                 requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
172             } else {  // Older versions not so much.
173                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
174                 requestHeadersBuilder.append("User-Agent");
175                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
176                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
177             }
178             requestHeadersBuilder.append(":  ");
179             requestHeadersBuilder.append(userAgent);
180
181
182             // Set the `x-requested-with` header property.
183             httpUrlConnection.setRequestProperty("x-requested-with", "");
184
185             // Add the `x-requested-with` header to the string builder and format the text.
186             requestHeadersBuilder.append(System.getProperty("line.separator"));
187             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
188                 requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
189             } else {  // Older versions not so much.
190                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
191                 requestHeadersBuilder.append("x-requested-with");
192                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
193                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
194             }
195             requestHeadersBuilder.append(":  ");
196
197
198             // Set the `Sec-Fetch-Site` header property.
199             httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
200
201             // Add the `Sec-Fetch-Site` header to the string builder and format the text.
202             requestHeadersBuilder.append(System.getProperty("line.separator"));
203             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
204                 requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
205             } else {  // Older versions not so much.
206                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
207                 requestHeadersBuilder.append("Sec-Fetch-Site");
208                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
209                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
210             }
211             requestHeadersBuilder.append(":  none");
212
213
214             // Set the `Sec-Fetch-Mode` header property.
215             httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
216
217             // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
218             requestHeadersBuilder.append(System.getProperty("line.separator"));
219             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
220                 requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
221             } else {  // Older versions not so much.
222                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
223                 requestHeadersBuilder.append("Sec-Fetch-Mode");
224                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
225                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
226             }
227             requestHeadersBuilder.append(":  navigate");
228
229
230             // Set the `Sec-Fetch-User` header property.
231             httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
232
233             // Add the `Sec-Fetch-User` header to the string builder and format the text.
234             requestHeadersBuilder.append(System.getProperty("line.separator"));
235             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
236                 requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
237             } else {  // Older versions not so much.
238                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
239                 requestHeadersBuilder.append("Sec-Fetch-User");
240                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
241                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
242             }
243             requestHeadersBuilder.append(":  ?1");
244
245
246             // Get a handle for the shared preferences.
247             SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext());
248
249             // Only populate `Do Not Track` if it is enabled.
250             if (sharedPreferences.getBoolean("do_not_track", false)) {
251                 // Set the `dnt` header property.
252                 httpUrlConnection.setRequestProperty("dnt", "1");
253
254                 // Add the `dnt` header to the string builder and format the text.
255                 requestHeadersBuilder.append(System.getProperty("line.separator"));
256                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
257                     requestHeadersBuilder.append("dnt", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
258                 } else {  // Older versions not so much.
259                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
260                     requestHeadersBuilder.append("dnt");
261                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
262                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
263                 }
264                 requestHeadersBuilder.append(":  1");
265             }
266
267
268             // Set the `Accept` header property.
269             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");
270
271             // Add the `Accept` header to the string builder and format the text.
272             requestHeadersBuilder.append(System.getProperty("line.separator"));
273             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
274                 requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
275             } else {  // Older versions not so much.
276                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
277                 requestHeadersBuilder.append("Accept");
278                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
279                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
280             }
281             requestHeadersBuilder.append(":  ");
282             requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
283
284
285             // Instantiate a locale string.
286             String localeString;
287
288             // Populate the locale string.
289             if (Build.VERSION.SDK_INT >= 24) {  // SDK >= 24 has a list of locales.
290                 // Get the list of locales.
291                 LocaleList localeList = activity.getResources().getConfiguration().getLocales();
292
293                 // Initialize a string builder to extract the locales from the list.
294                 StringBuilder localesStringBuilder = new StringBuilder();
295
296                 // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages.
297                 int q = 10;
298
299                 // Populate the string builder with the contents of the locales list.
300                 for (int i = 0; i < localeList.size(); i++) {
301                     // Append a comma if there is already an item in the string builder.
302                     if (i > 0) {
303                         localesStringBuilder.append(",");
304                     }
305
306                     // Get the locale from the list.
307                     Locale locale = localeList.get(i);
308
309                     // Add the locale to the string.  `locale` by default displays as `en_US`, but WebView uses the `en-US` format.
310                     localesStringBuilder.append(locale.getLanguage());
311                     localesStringBuilder.append("-");
312                     localesStringBuilder.append(locale.getCountry());
313
314                     // If not the first locale, append `;q=0.x`, which drops by .1 for each removal from the main locale until q=0.1.
315                     if (q < 10) {
316                         localesStringBuilder.append(";q=0.");
317                         localesStringBuilder.append(q);
318                     }
319
320                     // Decrement `q` if it is greater than 1.
321                     if (q > 1) {
322                         q--;
323                     }
324
325                     // Add a second entry for the language only portion of the locale.
326                     localesStringBuilder.append(",");
327                     localesStringBuilder.append(locale.getLanguage());
328
329                     // Append `1;q=0.x`, which drops by .1 for each removal form the main locale until q=0.1.
330                     localesStringBuilder.append(";q=0.");
331                     localesStringBuilder.append(q);
332
333                     // Decrement `q` if it is greater than 1.
334                     if (q > 1) {
335                         q--;
336                     }
337                 }
338
339                 // Store the populated string builder in the locale string.
340                 localeString = localesStringBuilder.toString();
341             } else {  // SDK < 24 only has a primary locale.
342                 // Store the locale in the locale string.
343                 localeString = Locale.getDefault().toString();
344             }
345
346             // Set the `Accept-Language` header property.
347             httpUrlConnection.setRequestProperty("Accept-Language", localeString);
348
349             // Add the `Accept-Language` header to the string builder and format the text.
350             requestHeadersBuilder.append(System.getProperty("line.separator"));
351             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
352                 requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
353             } else {  // Older versions not so much.
354                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
355                 requestHeadersBuilder.append("Accept-Language");
356                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
357                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
358             }
359             requestHeadersBuilder.append(":  ");
360             requestHeadersBuilder.append(localeString);
361
362
363             // Get the cookies for the current domain.
364             String cookiesString = CookieManager.getInstance().getCookie(url.toString());
365
366             // Only process the cookies if they are not null.
367             if (cookiesString != null) {
368                 // Set the `Cookie` header property.
369                 httpUrlConnection.setRequestProperty("Cookie", cookiesString);
370
371                 // Add the `Cookie` header to the string builder and format the text.
372                 requestHeadersBuilder.append(System.getProperty("line.separator"));
373                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
374                     requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
375                 } else {  // Older versions not so much.
376                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
377                     requestHeadersBuilder.append("Cookie");
378                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
379                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
380                 }
381                 requestHeadersBuilder.append(":  ");
382                 requestHeadersBuilder.append(cookiesString);
383             }
384
385
386             // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
387             // Add the `Accept-Encoding` header to the string builder and format the text.
388             requestHeadersBuilder.append(System.getProperty("line.separator"));
389             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
390                 requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
391             } else {  // Older versions not so much.
392                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
393                 requestHeadersBuilder.append("Accept-Encoding");
394                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
395                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
396             }
397             requestHeadersBuilder.append(":  gzip");
398
399
400             // 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.
401             try {
402                 // Initialize the string builders.
403                 responseMessageBuilder = new SpannableStringBuilder();
404                 responseHeadersBuilder = new SpannableStringBuilder();
405
406                 // Get the response code, which causes the connection to the server to be made.
407                 int responseCode = httpUrlConnection.getResponseCode();
408
409                 // Populate the response message string builder.
410                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
411                     responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
412                 } else {  // Older versions not so much.
413                     responseMessageBuilder.append(String.valueOf(responseCode));
414                     int newLength = responseMessageBuilder.length();
415                     responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
416                 }
417                 responseMessageBuilder.append(":  ");
418                 responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
419
420                 // Initialize the iteration variable.
421                 int i = 0;
422
423                 // Iterate through the received header fields.
424                 while (httpUrlConnection.getHeaderField(i) != null) {
425                     // Add a new line if there is already information in the string builder.
426                     if (i > 0) {
427                         responseHeadersBuilder.append(System.getProperty("line.separator"));
428                     }
429
430                     // Add the header to the string builder and format the text.
431                     if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
432                         responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
433                     } else {  // Older versions not so much.
434                         int oldLength = responseHeadersBuilder.length();
435                         responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
436                         int newLength = responseHeadersBuilder.length();
437                         responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
438                     }
439                     responseHeadersBuilder.append(":  ");
440                     responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
441
442                     // Increment the iteration variable.
443                     i++;
444                 }
445
446                 // Instantiate an input stream for the response body.
447                 InputStream inputStream;
448
449                 // Get the correct input stream based on the response code.
450                 if (responseCode == 404) {  // Get the error stream.
451                     inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
452                 } else {  // Get the response body stream.
453                     inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
454                 }
455
456                 // Initialize the byte array output stream and the conversion buffer byte array.
457                 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
458                 byte[] conversionBufferByteArray = new byte[1024];
459
460                 // Instantiate the variable to track the buffer length.
461                 int bufferLength;
462
463                 try {
464                     // 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.
465                     while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer is > 0.
466                         // Write the contents of the conversion buffer to the byte array output stream.
467                         byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
468                     }
469                 } catch (IOException e) {
470                     e.printStackTrace();
471                 }
472
473                 // Close the input stream.
474                 inputStream.close();
475
476                 // Populate the response body string with the contents of the byte array output stream.
477                 responseBodyBuilder.append(byteArrayOutputStream.toString());
478             } finally {
479                 // Disconnect `httpUrlConnection`.
480                 httpUrlConnection.disconnect();
481             }
482         } catch (IOException e) {
483             e.printStackTrace();
484         }
485
486         // Return the response body string as the result.
487         return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
488     }
489
490     // `onPostExecute()` operates on the UI thread.
491     @Override
492     protected void onPostExecute(SpannableStringBuilder[] viewSourceStringArray){
493         // Get a handle for the activity.
494         Activity activity = activityWeakReference.get();
495
496         // Abort if the activity is gone.
497         if ((activity == null) || activity.isFinishing()) {
498             return;
499         }
500
501         // Get handles for the text views.
502         TextView requestHeadersTextView = activity.findViewById(R.id.request_headers);
503         TextView responseMessageTextView = activity.findViewById(R.id.response_message);
504         TextView responseHeadersTextView = activity.findViewById(R.id.response_headers);
505         TextView responseBodyTextView = activity.findViewById(R.id.response_body);
506         ProgressBar progressBar = activity.findViewById(R.id.progress_bar);
507         SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.view_source_swiperefreshlayout);
508
509         // Populate the text views.  This can take a long time, and freeze the user interface, if the response body is particularly large.
510         requestHeadersTextView.setText(viewSourceStringArray[0]);
511         responseMessageTextView.setText(viewSourceStringArray[1]);
512         responseHeadersTextView.setText(viewSourceStringArray[2]);
513         responseBodyTextView.setText(viewSourceStringArray[3]);
514
515         // Hide the progress bar.
516         progressBar.setIndeterminate(false);
517         progressBar.setVisibility(View.GONE);
518
519         //Stop the swipe to refresh indicator if it is running
520         swipeRefreshLayout.setRefreshing(false);
521     }
522 }