2 * Copyright © 2017-2021 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, 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 // 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");
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);
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");
207 // Set the `Accept-Language` header property.
208 httpUrlConnection.setRequestProperty("Accept-Language", localeString);
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);
220 requestHeadersBuilder.append(": ");
221 requestHeadersBuilder.append(localeString);
224 // Get the cookies for the current domain.
225 String cookiesString = CookieManager.getInstance().getCookie(url.toString());
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);
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);
242 requestHeadersBuilder.append(": ");
243 requestHeadersBuilder.append(cookiesString);
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);
258 requestHeadersBuilder.append(": gzip");
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.
263 // Initialize the string builders.
264 responseMessageBuilder = new SpannableStringBuilder();
265 responseHeadersBuilder = new SpannableStringBuilder();
267 // Get the response code, which causes the connection to the server to be made.
268 int responseCode = httpUrlConnection.getResponseCode();
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);
278 responseMessageBuilder.append(": ");
279 responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
281 // Initialize the iteration variable.
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.
288 responseHeadersBuilder.append(System.getProperty("line.separator"));
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);
300 responseHeadersBuilder.append(": ");
301 responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
303 // Increment the iteration variable.
307 // Instantiate an input stream for the response body.
308 InputStream inputStream;
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());
317 // Initialize the byte array output stream and the conversion buffer byte array.
318 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
319 byte[] conversionBufferByteArray = new byte[1024];
321 // Define the buffer length variable.
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);
330 } catch (IOException exception) {
331 // Return the error message.
332 webViewSource.returnError(exception.toString());
335 // Close the input stream.
338 // Populate the response body string with the contents of the byte array output stream.
339 responseBodyBuilder.append(byteArrayOutputStream.toString());
341 // Disconnect HTTP URL connection.
342 httpUrlConnection.disconnect();
344 } catch (Exception exception) {
345 // Return the error message.
346 webViewSource.returnError(exception.toString());
349 // Return the response body string as the result.
350 return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};