2 * Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
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.
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.
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/>.
20 package com.stoutner.privacybrowser.backgroundtasks;
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;
29 import com.stoutner.privacybrowser.viewmodels.WebViewSource;
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;
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();
47 // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
49 // Get the current URL from the main activity.
50 URL url = new URL(urlString);
52 // Open a connection to the URL. No data is actually sent at this point.
53 HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
55 // Define the variables necessary to build the request headers.
56 requestHeadersBuilder = new SpannableStringBuilder();
57 int oldRequestHeadersBuilderLength;
58 int newRequestHeadersBuilderLength;
61 // Set the `Host` header property.
62 httpUrlConnection.setRequestProperty("Host", url.getHost());
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);
73 requestHeadersBuilder.append(": ");
74 requestHeadersBuilder.append(url.getHost());
77 // Set the `Connection` header property.
78 httpUrlConnection.setRequestProperty("Connection", "keep-alive");
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);
90 requestHeadersBuilder.append(": keep-alive");
93 // Set the `Upgrade-Insecure-Requests` header property.
94 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
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);
106 requestHeadersBuilder.append(": 1");
109 // Set the `User-Agent` header property.
110 httpUrlConnection.setRequestProperty("User-Agent", userAgent);
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);
122 requestHeadersBuilder.append(": ");
123 requestHeadersBuilder.append(userAgent);
126 // Set the `x-requested-with` header property.
127 httpUrlConnection.setRequestProperty("x-requested-with", "");
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);
139 requestHeadersBuilder.append(": ");
142 // Set the `Sec-Fetch-Site` header property.
143 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
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);
155 requestHeadersBuilder.append(": none");
158 // Set the `Sec-Fetch-Mode` header property.
159 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
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);
171 requestHeadersBuilder.append(": navigate");
174 // Set the `Sec-Fetch-User` header property.
175 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
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);
187 requestHeadersBuilder.append(": ?1");
190 // Only populate `Do Not Track` if it is enabled.
192 // Set the `dnt` header property.
193 httpUrlConnection.setRequestProperty("dnt", "1");
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);
205 requestHeadersBuilder.append(": 1");
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");
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);
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");
226 // Set the `Accept-Language` header property.
227 httpUrlConnection.setRequestProperty("Accept-Language", localeString);
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);
239 requestHeadersBuilder.append(": ");
240 requestHeadersBuilder.append(localeString);
243 // Get the cookies for the current domain.
244 String cookiesString = CookieManager.getInstance().getCookie(url.toString());
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);
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);
261 requestHeadersBuilder.append(": ");
262 requestHeadersBuilder.append(cookiesString);
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);
277 requestHeadersBuilder.append(": gzip");
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.
282 // Initialize the string builders.
283 responseMessageBuilder = new SpannableStringBuilder();
284 responseHeadersBuilder = new SpannableStringBuilder();
286 // Get the response code, which causes the connection to the server to be made.
287 int responseCode = httpUrlConnection.getResponseCode();
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);
297 responseMessageBuilder.append(": ");
298 responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
300 // Initialize the iteration variable.
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.
307 responseHeadersBuilder.append(System.getProperty("line.separator"));
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);
319 responseHeadersBuilder.append(": ");
320 responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
322 // Increment the iteration variable.
326 // Instantiate an input stream for the response body.
327 InputStream inputStream;
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());
336 // Initialize the byte array output stream and the conversion buffer byte array.
337 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
338 byte[] conversionBufferByteArray = new byte[1024];
340 // Define the buffer length variable.
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);
349 } catch (IOException exception) {
350 // Return the error message.
351 webViewSource.returnError(exception.toString());
354 // Close the input stream.
357 // Populate the response body string with the contents of the byte array output stream.
358 responseBodyBuilder.append(byteArrayOutputStream.toString());
360 // Disconnect HTTP URL connection.
361 httpUrlConnection.disconnect();
363 } catch (Exception exception) {
364 // Return the error message.
365 webViewSource.returnError(exception.toString());
368 // Return the response body string as the result.
369 return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};