3ecac1d6ff3d4c174a94ff67264252a34936d85a
[PrivacyBrowser.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.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, 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             // Set the `Accept` header property.
191             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");
192
193             // Add the `Accept` header to the string builder and format the text.
194             requestHeadersBuilder.append(System.getProperty("line.separator"));
195             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
196                 requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
197             } else {  // Older versions not so much.
198                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
199                 requestHeadersBuilder.append("Accept");
200                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
201                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
202             }
203             requestHeadersBuilder.append(":  ");
204             requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
205
206
207             // Set the `Accept-Language` header property.
208             httpUrlConnection.setRequestProperty("Accept-Language", localeString);
209
210             // Add the `Accept-Language` header to the string builder and format the text.
211             requestHeadersBuilder.append(System.getProperty("line.separator"));
212             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
213                 requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
214             } else {  // Older versions not so much.
215                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
216                 requestHeadersBuilder.append("Accept-Language");
217                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
218                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
219             }
220             requestHeadersBuilder.append(":  ");
221             requestHeadersBuilder.append(localeString);
222
223
224             // Get the cookies for the current domain.
225             String cookiesString = CookieManager.getInstance().getCookie(url.toString());
226
227             // Only process the cookies if they are not null.
228             if (cookiesString != null) {
229                 // Add the cookies to the header property.
230                 httpUrlConnection.setRequestProperty("Cookie", cookiesString);
231
232                 // Add the cookie header to the string builder and format the text.
233                 requestHeadersBuilder.append(System.getProperty("line.separator"));
234                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
235                     requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
236                 } else {  // Older versions not so much.
237                     oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
238                     requestHeadersBuilder.append("Cookie");
239                     newRequestHeadersBuilderLength = requestHeadersBuilder.length();
240                     requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
241                 }
242                 requestHeadersBuilder.append(":  ");
243                 requestHeadersBuilder.append(cookiesString);
244             }
245
246
247             // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
248             // Add the `Accept-Encoding` header to the string builder and format the text.
249             requestHeadersBuilder.append(System.getProperty("line.separator"));
250             if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
251                 requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
252             } else {  // Older versions not so much.
253                 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
254                 requestHeadersBuilder.append("Accept-Encoding");
255                 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
256                 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
257             }
258             requestHeadersBuilder.append(":  gzip");
259
260
261             // 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.
262             try {
263                 // Initialize the string builders.
264                 responseMessageBuilder = new SpannableStringBuilder();
265                 responseHeadersBuilder = new SpannableStringBuilder();
266
267                 // Get the response code, which causes the connection to the server to be made.
268                 int responseCode = httpUrlConnection.getResponseCode();
269
270                 // Populate the response message string builder.
271                 if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
272                     responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
273                 } else {  // Older versions not so much.
274                     responseMessageBuilder.append(String.valueOf(responseCode));
275                     int newLength = responseMessageBuilder.length();
276                     responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
277                 }
278                 responseMessageBuilder.append(":  ");
279                 responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
280
281                 // Initialize the iteration variable.
282                 int i = 0;
283
284                 // Iterate through the received header fields.
285                 while (httpUrlConnection.getHeaderField(i) != null) {
286                     // Add a new line if there is already information in the string builder.
287                     if (i > 0) {
288                         responseHeadersBuilder.append(System.getProperty("line.separator"));
289                     }
290
291                     // Add the header to the string builder and format the text.
292                     if (Build.VERSION.SDK_INT >= 21) {  // Newer versions of Android are so smart.
293                         responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
294                     } else {  // Older versions not so much.
295                         int oldLength = responseHeadersBuilder.length();
296                         responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
297                         int newLength = responseHeadersBuilder.length();
298                         responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
299                     }
300                     responseHeadersBuilder.append(":  ");
301                     responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
302
303                     // Increment the iteration variable.
304                     i++;
305                 }
306
307                 // Instantiate an input stream for the response body.
308                 InputStream inputStream;
309
310                 // Get the correct input stream based on the response code.
311                 if (responseCode == 404) {  // Get the error stream.
312                     inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
313                 } else {  // Get the response body stream.
314                     inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
315                 }
316
317                 // Initialize the byte array output stream and the conversion buffer byte array.
318                 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
319                 byte[] conversionBufferByteArray = new byte[1024];
320
321                 // Define the buffer length variable.
322                 int bufferLength;
323
324                 try {
325                     // 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.
326                     while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer is > 0.
327                         // Write the contents of the conversion buffer to the byte array output stream.
328                         byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
329                     }
330                 } catch (IOException exception) {
331                     // Return the error message.
332                     webViewSource.returnError(exception.toString());
333                 }
334
335                 // Close the input stream.
336                 inputStream.close();
337
338                 // Populate the response body string with the contents of the byte array output stream.
339                 responseBodyBuilder.append(byteArrayOutputStream.toString());
340             } finally {
341                 // Disconnect HTTP URL connection.
342                 httpUrlConnection.disconnect();
343             }
344         } catch (Exception exception) {
345             // Return the error message.
346             webViewSource.returnError(exception.toString());
347         }
348
349         // Return the response body string as the result.
350         return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
351     }
352 }