]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/backgroundtasks/GetSourceBackgroundTask.java
Allow View Source to connect to untrusted SSL certificates. https://redmine.stoutner...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / backgroundtasks / GetSourceBackgroundTask.java
1 /*
2  * Copyright © 2017-2021 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.backgroundtasks;
21
22 import android.annotation.SuppressLint;
23 import android.content.ContentResolver;
24 import android.database.Cursor;
25 import android.graphics.Typeface;
26 import android.net.Uri;
27 import android.os.Build;
28 import android.text.SpannableStringBuilder;
29 import android.text.Spanned;
30 import android.text.style.StyleSpan;
31 import android.webkit.CookieManager;
32
33 import com.stoutner.privacybrowser.viewmodels.WebViewSource;
34
35 import java.io.BufferedInputStream;
36 import java.io.BufferedReader;
37 import java.io.ByteArrayOutputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.InputStreamReader;
41 import java.net.HttpURLConnection;
42 import java.net.Proxy;
43 import java.net.URL;
44 import java.security.SecureRandom;
45 import java.security.cert.X509Certificate;
46
47 import javax.net.ssl.HostnameVerifier;
48 import javax.net.ssl.HttpsURLConnection;
49 import javax.net.ssl.SSLContext;
50 import javax.net.ssl.SSLSocketFactory;
51 import javax.net.ssl.TrustManager;
52 import javax.net.ssl.X509TrustManager;
53
54 public class GetSourceBackgroundTask {
55     public SpannableStringBuilder[] acquire(String urlString, String userAgent, String localeString, Proxy proxy, ContentResolver contentResolver, WebViewSource webViewSource, boolean ignoreSslErrors) {
56         // Initialize the spannable string builders.
57         SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
58         SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
59         SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
60         SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
61
62         if (urlString.startsWith("content://")) {  // This is a content URL.
63             // Attempt to read the content data.  Return an error if this fails.
64             try {
65                 // Get a URI for the content URL.
66                 Uri contentUri = Uri.parse(urlString);
67
68                 // Define the variables necessary to build the response headers.
69                 int oldResponseHeadersBuilderLength;
70                 int newResponseHeadersBuilderLength;
71
72                 // Get a cursor with metadata about the content URL.
73                 Cursor contentCursor = contentResolver.query(contentUri, null, null, null, null);
74
75                 // Move the content cursor to the first row.
76                 contentCursor.moveToFirst();
77
78                 for (int i = 0; i < contentCursor.getColumnCount(); i++) {
79                     // Add a new line if this is not the first entry.
80                     if (i > 0) {
81                         responseHeadersBuilder.append(System.getProperty("line.separator"));
82                     }
83
84                     // Add each header to the string builder.
85                     if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
86                         responseHeadersBuilder.append(contentCursor.getColumnName(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
87                     } else {  // Older versions are not so much.
88                         oldResponseHeadersBuilderLength = responseHeadersBuilder.length();
89                         responseHeadersBuilder.append(contentCursor.getColumnName(i));
90                         newResponseHeadersBuilderLength = responseHeadersBuilder.length();
91                         responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldResponseHeadersBuilderLength, newResponseHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
92                     }
93                     responseHeadersBuilder.append(":  ");
94                     responseHeadersBuilder.append(contentCursor.getString(i));
95                 }
96
97                 // Close the content cursor.
98                 contentCursor.close();
99
100                 // Create a buffered string reader for the content data.
101                 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(contentResolver.openInputStream(contentUri)));
102
103                 // Get the data from the buffered reader one line at a time.
104                 for (String contentLineString; ((contentLineString = bufferedReader.readLine()) != null);) {
105                     // Add the line to the response body builder.
106                     responseBodyBuilder.append(contentLineString);
107
108                     // Append a new line.
109                     responseBodyBuilder.append("\n");
110                 }
111             } catch (Exception exception) {
112                 // Return the error message.
113                 webViewSource.returnError(exception.toString());
114             }
115         } else {  // This is not a content URL.
116             // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
117             try {
118                 // Get the current URL from the main activity.
119                 URL url = new URL(urlString);
120
121                 // Open a connection to the URL.  No data is actually sent at this point.
122                 HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
123
124                 // Define the variables necessary to build the request headers.
125                 int oldRequestHeadersBuilderLength;
126                 int newRequestHeadersBuilderLength;
127
128
129                 // Set the `Host` header property.
130                 httpUrlConnection.setRequestProperty("Host", url.getHost());
131
132                 // Add the `Host` header to the string builder and format the text.
133                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
134                     requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
135                 } else {  // Older versions not so much.
136                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
137                     requestHeadersBuilder.append("Host");
138                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
139                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
140                 }
141                 requestHeadersBuilder.append(":  ");
142                 requestHeadersBuilder.append(url.getHost());
143
144
145                 // Set the `Connection` header property.
146                 httpUrlConnection.setRequestProperty("Connection", "keep-alive");
147
148                 // Add the `Connection` header to the string builder and format the text.
149                 requestHeadersBuilder.append(System.getProperty("line.separator"));
150                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
151                     requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
152                 } else {  // Older versions not so much.
153                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
154                     requestHeadersBuilder.append("Connection");
155                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
156                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
157                 }
158                 requestHeadersBuilder.append(":  keep-alive");
159
160
161                 // Set the `Upgrade-Insecure-Requests` header property.
162                 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
163
164                 // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
165                 requestHeadersBuilder.append(System.getProperty("line.separator"));
166                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
167                     requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
168                 } else {  // Older versions not so much.
169                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
170                     requestHeadersBuilder.append("Upgrade-Insecure_Requests");
171                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
172                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
173                 }
174                 requestHeadersBuilder.append(":  1");
175
176
177                 // Set the `User-Agent` header property.
178                 httpUrlConnection.setRequestProperty("User-Agent", userAgent);
179
180                 // Add the `User-Agent` header to the string builder and format the text.
181                 requestHeadersBuilder.append(System.getProperty("line.separator"));
182                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
183                     requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
184                 } else {  // Older versions not so much.
185                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
186                     requestHeadersBuilder.append("User-Agent");
187                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
188                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
189                 }
190                 requestHeadersBuilder.append(":  ");
191                 requestHeadersBuilder.append(userAgent);
192
193
194                 // Set the `x-requested-with` header property.
195                 httpUrlConnection.setRequestProperty("x-requested-with", "");
196
197                 // Add the `x-requested-with` header to the string builder and format the text.
198                 requestHeadersBuilder.append(System.getProperty("line.separator"));
199                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
200                     requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
201                 } else {  // Older versions not so much.
202                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
203                     requestHeadersBuilder.append("x-requested-with");
204                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
205                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
206                 }
207                 requestHeadersBuilder.append(":  ");
208
209
210                 // Set the `Sec-Fetch-Site` header property.
211                 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
212
213                 // Add the `Sec-Fetch-Site` header to the string builder and format the text.
214                 requestHeadersBuilder.append(System.getProperty("line.separator"));
215                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
216                     requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
217                 } else {  // Older versions not so much.
218                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
219                     requestHeadersBuilder.append("Sec-Fetch-Site");
220                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
221                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
222                 }
223                 requestHeadersBuilder.append(":  none");
224
225
226                 // Set the `Sec-Fetch-Mode` header property.
227                 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
228
229                 // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
230                 requestHeadersBuilder.append(System.getProperty("line.separator"));
231                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
232                     requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
233                 } else {  // Older versions not so much.
234                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
235                     requestHeadersBuilder.append("Sec-Fetch-Mode");
236                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
237                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
238                 }
239                 requestHeadersBuilder.append(":  navigate");
240
241
242                 // Set the `Sec-Fetch-User` header property.
243                 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
244
245                 // Add the `Sec-Fetch-User` header to the string builder and format the text.
246                 requestHeadersBuilder.append(System.getProperty("line.separator"));
247                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
248                     requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
249                 } else {  // Older versions not so much.
250                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
251                     requestHeadersBuilder.append("Sec-Fetch-User");
252                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
253                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
254                 }
255                 requestHeadersBuilder.append(":  ?1");
256
257
258                 // Set the `Accept` header property.
259                 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");
260
261                 // Add the `Accept` header to the string builder and format the text.
262                 requestHeadersBuilder.append(System.getProperty("line.separator"));
263                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
264                     requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
265                 } else {  // Older versions not so much.
266                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
267                     requestHeadersBuilder.append("Accept");
268                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
269                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
270                 }
271                 requestHeadersBuilder.append(":  ");
272                 requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
273
274
275                 // Set the `Accept-Language` header property.
276                 httpUrlConnection.setRequestProperty("Accept-Language", localeString);
277
278                 // Add the `Accept-Language` header to the string builder and format the text.
279                 requestHeadersBuilder.append(System.getProperty("line.separator"));
280                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
281                     requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
282                 } else {  // Older versions not so much.
283                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
284                     requestHeadersBuilder.append("Accept-Language");
285                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
286                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
287                 }
288                 requestHeadersBuilder.append(":  ");
289                 requestHeadersBuilder.append(localeString);
290
291
292                 // Get the cookies for the current domain.
293                 String cookiesString = CookieManager.getInstance().getCookie(url.toString());
294
295                 // Only process the cookies if they are not null.
296                 if (cookiesString != null) {
297                     // Add the cookies to the header property.
298                     httpUrlConnection.setRequestProperty("Cookie", cookiesString);
299
300                     // Add the cookie header to the string builder and format the text.
301                     requestHeadersBuilder.append(System.getProperty("line.separator"));
302                     if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
303                         requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
304                     } else {  // Older versions not so much.
305                         oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
306                         requestHeadersBuilder.append("Cookie");
307                         newRequestHeadersBuilderLength = requestHeadersBuilder.length();
308                         requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
309                     }
310                     requestHeadersBuilder.append(":  ");
311                     requestHeadersBuilder.append(cookiesString);
312                 }
313
314
315                 // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
316                 // Add the `Accept-Encoding` header to the string builder and format the text.
317                 requestHeadersBuilder.append(System.getProperty("line.separator"));
318                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
319                     requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
320                 } else {  // Older versions not so much.
321                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
322                     requestHeadersBuilder.append("Accept-Encoding");
323                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
324                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
325                 }
326                 requestHeadersBuilder.append(":  gzip");
327
328                 // Ignore SSL errors if requested.
329                 if (ignoreSslErrors){
330                     // Create a new host name verifier.
331                     HostnameVerifier hostnameVerifier = (hostname, sslSession) -> {
332                         // Allow all host names.
333                         return true;
334                     };
335
336                     // Create a new trust manager.
337                     TrustManager[] trustManager = new TrustManager[] {
338                             new X509TrustManager() {
339                                 @SuppressLint("TrustAllX509TrustManager")
340                                 @Override
341                                 public void checkClientTrusted(X509Certificate[] chain, String authType) {
342                                     // Do nothing, which trusts all client certificates.
343                                 }
344
345                                 @SuppressLint("TrustAllX509TrustManager")
346                                 @Override
347                                 public void checkServerTrusted(X509Certificate[] chain, String authType) {
348                                     // Do nothing, which trusts all server certificates.
349                                 }
350
351                                 @Override
352                                 public X509Certificate[] getAcceptedIssuers() {
353                                     return null;
354                                 }
355                             }
356                     };
357
358                     // Get an SSL context.  `TLS` provides a base instance available from API 1.  <https://developer.android.com/reference/javax/net/ssl/SSLContext>
359                     SSLContext sslContext = SSLContext.getInstance("TLS");
360
361                     // Initialize the SSL context with the blank trust manager.
362                     sslContext.init(null, trustManager, new SecureRandom());
363
364                     // Get the SSL socket factory with the blank trust manager.
365                     SSLSocketFactory socketFactory = sslContext.getSocketFactory();
366
367                     // Set the HTTPS URL Connection to use the blank host name verifier.
368                     ((HttpsURLConnection) httpUrlConnection).setHostnameVerifier(hostnameVerifier);
369
370                     // Set the HTTPS URL connection to use the socket factory with the blank trust manager.
371                     ((HttpsURLConnection) httpUrlConnection).setSSLSocketFactory(socketFactory);
372                 }
373
374                 // 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.
375                 try {
376                     // Get the response code, which causes the connection to the server to be made.
377                     int responseCode = httpUrlConnection.getResponseCode();
378
379                     // Populate the response message string builder.
380                     if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
381                         responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
382                     } else {  // Older versions not so much.
383                         responseMessageBuilder.append(String.valueOf(responseCode));
384                         int newLength = responseMessageBuilder.length();
385                         responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
386                     }
387                     responseMessageBuilder.append(":  ");
388                     responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
389
390                     // Initialize the iteration variable.
391                     int i = 0;
392
393                     // Iterate through the received header fields.
394                     while (httpUrlConnection.getHeaderField(i) != null) {
395                         // Add a new line if there is already information in the string builder.
396                         if (i > 0) {
397                             responseHeadersBuilder.append(System.getProperty("line.separator"));
398                         }
399
400                         // Add the header to the string builder and format the text.
401                         if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
402                             responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
403                         } else {  // Older versions not so much.
404                             int oldLength = responseHeadersBuilder.length();
405                             responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
406                             int newLength = responseHeadersBuilder.length();
407                             responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
408                         }
409                         responseHeadersBuilder.append(":  ");
410                         responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
411
412                         // Increment the iteration variable.
413                         i++;
414                     }
415
416                     // Instantiate an input stream for the response body.
417                     InputStream inputStream;
418
419                     // Get the correct input stream based on the response code.
420                     if (responseCode == 404) {  // Get the error stream.
421                         inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
422                     } else {  // Get the response body stream.
423                         inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
424                     }
425
426                     // Initialize the byte array output stream and the conversion buffer byte array.
427                     ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
428                     byte[] conversionBufferByteArray = new byte[1024];
429
430                     // Define the buffer length variable.
431                     int bufferLength;
432
433                     try {
434                         // 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.
435                         while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer is > 0.
436                             // Write the contents of the conversion buffer to the byte array output stream.
437                             byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
438                         }
439                     } catch (IOException exception) {
440                         // Return the error message.
441                         webViewSource.returnError(exception.toString());
442                     }
443
444                     // Close the input stream.
445                     inputStream.close();
446
447                     // Populate the response body string with the contents of the byte array output stream.
448                     responseBodyBuilder.append(byteArrayOutputStream.toString());
449                 } finally {
450                     // Disconnect HTTP URL connection.
451                     httpUrlConnection.disconnect();
452                 }
453             } catch (Exception exception) {
454                 // Return the error message.
455                 webViewSource.returnError(exception.toString());
456             }
457         }
458
459         // Return the spannable string builders.
460         return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
461     }
462 }