Fix View Source crashing on release builds. https://redmine.stoutner.com/issues/642
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / backgroundtasks / GetSourceBackgroundTask.java
1 /*
2  * Copyright © 2017-2020 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.graphics.Typeface;
23 import android.os.Build;
24 import android.text.SpannableStringBuilder;
25 import android.text.Spanned;
26 import android.text.style.StyleSpan;
27 import android.webkit.CookieManager;
28
29 import com.stoutner.privacybrowser.viewmodels.WebViewSource;
30
31 import java.io.BufferedInputStream;
32 import java.io.ByteArrayOutputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.net.HttpURLConnection;
36 import java.net.Proxy;
37 import java.net.URL;
38
39 public class GetSourceBackgroundTask {
40     public SpannableStringBuilder[] acquire(String urlString, String userAgent, boolean doNotTrack, String localeString, Proxy proxy, WebViewSource webViewSource) {
41         // Initialize the spannable string builders.
42         SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
43         SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
44         SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
45         SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
46
47         // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
48         try {
49             // Get the current URL from the main activity.
50             URL url = new URL(urlString);
51
52             // Open a connection to the URL.  No data is actually sent at this point.
53             HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
54
55             // Define the variables necessary to build the request headers.
56             requestHeadersBuilder = new SpannableStringBuilder();
57             int oldRequestHeadersBuilderLength;
58             int newRequestHeadersBuilderLength;
59
60
61             // Set the `Host` header property.
62             httpUrlConnection.setRequestProperty("Host", url.getHost());
63
64             // Add the `Host` header to the string builder and format the text.
65             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
66                 requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
67             } else {  // Older versions not so much.
68                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
69                 requestHeadersBuilder.append("Host");
70                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
71                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
72             }
73             requestHeadersBuilder.append(":  ");
74             requestHeadersBuilder.append(url.getHost());
75
76
77             // Set the `Connection` header property.
78             httpUrlConnection.setRequestProperty("Connection", "keep-alive");
79
80             // Add the `Connection` header to the string builder and format the text.
81             requestHeadersBuilder.append(System.getProperty("line.separator"));
82             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
83                 requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
84             } else {  // Older versions not so much.
85                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
86                 requestHeadersBuilder.append("Connection");
87                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
88                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
89             }
90             requestHeadersBuilder.append(":  keep-alive");
91
92
93             // Set the `Upgrade-Insecure-Requests` header property.
94             httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
95
96             // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
97             requestHeadersBuilder.append(System.getProperty("line.separator"));
98             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
99                 requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
100             } else {  // Older versions not so much.
101                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
102                 requestHeadersBuilder.append("Upgrade-Insecure_Requests");
103                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
104                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
105             }
106             requestHeadersBuilder.append(":  1");
107
108
109             // Set the `User-Agent` header property.
110             httpUrlConnection.setRequestProperty("User-Agent", userAgent);
111
112             // Add the `User-Agent` header to the string builder and format the text.
113             requestHeadersBuilder.append(System.getProperty("line.separator"));
114             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
115                 requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
116             } else {  // Older versions not so much.
117                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
118                 requestHeadersBuilder.append("User-Agent");
119                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
120                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
121             }
122             requestHeadersBuilder.append(":  ");
123             requestHeadersBuilder.append(userAgent);
124
125
126             // Set the `x-requested-with` header property.
127             httpUrlConnection.setRequestProperty("x-requested-with", "");
128
129             // Add the `x-requested-with` header to the string builder and format the text.
130             requestHeadersBuilder.append(System.getProperty("line.separator"));
131             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
132                 requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
133             } else {  // Older versions not so much.
134                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
135                 requestHeadersBuilder.append("x-requested-with");
136                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
137                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
138             }
139             requestHeadersBuilder.append(":  ");
140
141
142             // Set the `Sec-Fetch-Site` header property.
143             httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
144
145             // Add the `Sec-Fetch-Site` header to the string builder and format the text.
146             requestHeadersBuilder.append(System.getProperty("line.separator"));
147             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
148                 requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
149             } else {  // Older versions not so much.
150                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
151                 requestHeadersBuilder.append("Sec-Fetch-Site");
152                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
153                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
154             }
155             requestHeadersBuilder.append(":  none");
156
157
158             // Set the `Sec-Fetch-Mode` header property.
159             httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
160
161             // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
162             requestHeadersBuilder.append(System.getProperty("line.separator"));
163             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
164                 requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
165             } else {  // Older versions not so much.
166                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
167                 requestHeadersBuilder.append("Sec-Fetch-Mode");
168                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
169                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
170             }
171             requestHeadersBuilder.append(":  navigate");
172
173
174             // Set the `Sec-Fetch-User` header property.
175             httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
176
177             // Add the `Sec-Fetch-User` header to the string builder and format the text.
178             requestHeadersBuilder.append(System.getProperty("line.separator"));
179             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
180                 requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
181             } else {  // Older versions not so much.
182                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
183                 requestHeadersBuilder.append("Sec-Fetch-User");
184                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
185                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
186             }
187             requestHeadersBuilder.append(":  ?1");
188
189
190             // Only populate `Do Not Track` if it is enabled.
191             if (doNotTrack) {
192                 // Set the `dnt` header property.
193                 httpUrlConnection.setRequestProperty("dnt", "1");
194
195                 // Add the `dnt` header to the string builder and format the text.
196                 requestHeadersBuilder.append(System.getProperty("line.separator"));
197                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
198                     requestHeadersBuilder.append("dnt", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
199                 } else {  // Older versions not so much.
200                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
201                     requestHeadersBuilder.append("dnt");
202                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
203                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
204                 }
205                 requestHeadersBuilder.append(":  1");
206             }
207
208
209             // Set the `Accept` header property.
210             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");
211
212             // Add the `Accept` header to the string builder and format the text.
213             requestHeadersBuilder.append(System.getProperty("line.separator"));
214             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
215                 requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
216             } else {  // Older versions not so much.
217                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
218                 requestHeadersBuilder.append("Accept");
219                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
220                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
221             }
222             requestHeadersBuilder.append(":  ");
223             requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
224
225
226             // Set the `Accept-Language` header property.
227             httpUrlConnection.setRequestProperty("Accept-Language", localeString);
228
229             // Add the `Accept-Language` 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("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
233             } else {  // Older versions not so much.
234                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
235                 requestHeadersBuilder.append("Accept-Language");
236                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
237                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
238             }
239             requestHeadersBuilder.append(":  ");
240             requestHeadersBuilder.append(localeString);
241
242
243             // Get the cookies for the current domain.
244             String cookiesString = CookieManager.getInstance().getCookie(url.toString());
245
246             // Only process the cookies if they are not null.
247             if (cookiesString != null) {
248                 // Add the cookies to the header property.
249                 httpUrlConnection.setRequestProperty("Cookie", cookiesString);
250
251                 // Add the cookie header to the string builder and format the text.
252                 requestHeadersBuilder.append(System.getProperty("line.separator"));
253                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
254                     requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
255                 } else {  // Older versions not so much.
256                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
257                     requestHeadersBuilder.append("Cookie");
258                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
259                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
260                 }
261                 requestHeadersBuilder.append(":  ");
262                 requestHeadersBuilder.append(cookiesString);
263             }
264
265
266             // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
267             // Add the `Accept-Encoding` header to the string builder and format the text.
268             requestHeadersBuilder.append(System.getProperty("line.separator"));
269             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
270                 requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
271             } else {  // Older versions not so much.
272                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
273                 requestHeadersBuilder.append("Accept-Encoding");
274                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
275                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
276             }
277             requestHeadersBuilder.append(":  gzip");
278
279
280             // 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.
281             try {
282                 // Initialize the string builders.
283                 responseMessageBuilder = new SpannableStringBuilder();
284                 responseHeadersBuilder = new SpannableStringBuilder();
285
286                 // Get the response code, which causes the connection to the server to be made.
287                 int responseCode = httpUrlConnection.getResponseCode();
288
289                 // Populate the response message string builder.
290                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
291                     responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
292                 } else {  // Older versions not so much.
293                     responseMessageBuilder.append(String.valueOf(responseCode));
294                     int newLength = responseMessageBuilder.length();
295                     responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
296                 }
297                 responseMessageBuilder.append(":  ");
298                 responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
299
300                 // Initialize the iteration variable.
301                 int i = 0;
302
303                 // Iterate through the received header fields.
304                 while (httpUrlConnection.getHeaderField(i) != null) {
305                     // Add a new line if there is already information in the string builder.
306                     if (i > 0) {
307                         responseHeadersBuilder.append(System.getProperty("line.separator"));
308                     }
309
310                     // Add the header to the string builder and format the text.
311                     if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
312                         responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
313                     } else {  // Older versions not so much.
314                         int oldLength = responseHeadersBuilder.length();
315                         responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
316                         int newLength = responseHeadersBuilder.length();
317                         responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
318                     }
319                     responseHeadersBuilder.append(":  ");
320                     responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
321
322                     // Increment the iteration variable.
323                     i++;
324                 }
325
326                 // Instantiate an input stream for the response body.
327                 InputStream inputStream;
328
329                 // Get the correct input stream based on the response code.
330                 if (responseCode == 404) {  // Get the error stream.
331                     inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
332                 } else {  // Get the response body stream.
333                     inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
334                 }
335
336                 // Initialize the byte array output stream and the conversion buffer byte array.
337                 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
338                 byte[] conversionBufferByteArray = new byte[1024];
339
340                 // Define the buffer length variable.
341                 int bufferLength;
342
343                 try {
344                     // 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.
345                     while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer is > 0.
346                         // Write the contents of the conversion buffer to the byte array output stream.
347                         byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
348                     }
349                 } catch (IOException exception) {
350                     // Return the error message.
351                     webViewSource.returnError(exception.toString());
352                 }
353
354                 // Close the input stream.
355                 inputStream.close();
356
357                 // Populate the response body string with the contents of the byte array output stream.
358                 responseBodyBuilder.append(byteArrayOutputStream.toString());
359             } finally {
360                 // Disconnect HTTP URL connection.
361                 httpUrlConnection.disconnect();
362             }
363         } catch (Exception exception) {
364             // Return the error message.
365             webViewSource.returnError(exception.toString());
366         }
367
368         // Return the response body string as the result.
369         return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
370     }
371 }