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.content.ContentResolver;
23 import android.database.Cursor;
24 import android.graphics.Typeface;
25 import android.net.Uri;
26 import android.os.Build;
27 import android.text.SpannableStringBuilder;
28 import android.text.Spanned;
29 import android.text.style.StyleSpan;
30 import android.webkit.CookieManager;
32 import com.stoutner.privacybrowser.viewmodels.WebViewSource;
34 import java.io.BufferedInputStream;
35 import java.io.BufferedReader;
36 import java.io.ByteArrayOutputStream;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.InputStreamReader;
40 import java.net.HttpURLConnection;
41 import java.net.Proxy;
44 public class GetSourceBackgroundTask {
45 public SpannableStringBuilder[] acquire(String urlString, String userAgent, String localeString, Proxy proxy, ContentResolver contentResolver, WebViewSource webViewSource) {
46 // Initialize the spannable string builders.
47 SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
48 SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
49 SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
50 SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
52 if (urlString.startsWith("content://")) { // This is a content URL.
53 // Attempt to read the content data. Return an error if this fails.
55 // Get a URI for the content URL.
56 Uri contentUri = Uri.parse(urlString);
58 // Define the variables necessary to build the response headers.
59 int oldResponseHeadersBuilderLength;
60 int newResponseHeadersBuilderLength;
62 // Get a cursor with metadata about the content URL.
63 Cursor contentCursor = contentResolver.query(contentUri, null, null, null, null);
65 // Move the content cursor to the first row.
66 contentCursor.moveToFirst();
68 for (int i = 0; i < contentCursor.getColumnCount(); i++) {
69 // Add a new line if this is not the first entry.
71 responseHeadersBuilder.append(System.getProperty("line.separator"));
74 // Add each header to the string builder.
75 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
76 responseHeadersBuilder.append(contentCursor.getColumnName(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
77 } else { // Older versions are not so much.
78 oldResponseHeadersBuilderLength = responseHeadersBuilder.length();
79 responseHeadersBuilder.append(contentCursor.getColumnName(i));
80 newResponseHeadersBuilderLength = responseHeadersBuilder.length();
81 responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldResponseHeadersBuilderLength, newResponseHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
83 responseHeadersBuilder.append(": ");
84 responseHeadersBuilder.append(contentCursor.getString(i));
87 // Close the content cursor.
88 contentCursor.close();
90 // Create a buffered string reader for the content data.
91 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(contentResolver.openInputStream(contentUri)));
93 // Get the data from the buffered reader one line at a time.
94 for (String contentLineString; ((contentLineString = bufferedReader.readLine()) != null);) {
95 // Add the line to the response body builder.
96 responseBodyBuilder.append(contentLineString);
99 responseBodyBuilder.append("\n");
101 } catch (Exception exception) {
102 // Return the error message.
103 webViewSource.returnError(exception.toString());
105 } else { // This is not a content URL.
106 // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
108 // Get the current URL from the main activity.
109 URL url = new URL(urlString);
111 // Open a connection to the URL. No data is actually sent at this point.
112 HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
114 // Define the variables necessary to build the request headers.
115 int oldRequestHeadersBuilderLength;
116 int newRequestHeadersBuilderLength;
119 // Set the `Host` header property.
120 httpUrlConnection.setRequestProperty("Host", url.getHost());
122 // Add the `Host` header to the string builder and format the text.
123 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
124 requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
125 } else { // Older versions not so much.
126 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
127 requestHeadersBuilder.append("Host");
128 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
129 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
131 requestHeadersBuilder.append(": ");
132 requestHeadersBuilder.append(url.getHost());
135 // Set the `Connection` header property.
136 httpUrlConnection.setRequestProperty("Connection", "keep-alive");
138 // Add the `Connection` header to the string builder and format the text.
139 requestHeadersBuilder.append(System.getProperty("line.separator"));
140 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
141 requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
142 } else { // Older versions not so much.
143 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
144 requestHeadersBuilder.append("Connection");
145 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
146 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
148 requestHeadersBuilder.append(": keep-alive");
151 // Set the `Upgrade-Insecure-Requests` header property.
152 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
154 // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
155 requestHeadersBuilder.append(System.getProperty("line.separator"));
156 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
157 requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
158 } else { // Older versions not so much.
159 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
160 requestHeadersBuilder.append("Upgrade-Insecure_Requests");
161 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
162 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
164 requestHeadersBuilder.append(": 1");
167 // Set the `User-Agent` header property.
168 httpUrlConnection.setRequestProperty("User-Agent", userAgent);
170 // Add the `User-Agent` header to the string builder and format the text.
171 requestHeadersBuilder.append(System.getProperty("line.separator"));
172 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
173 requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
174 } else { // Older versions not so much.
175 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
176 requestHeadersBuilder.append("User-Agent");
177 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
178 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
180 requestHeadersBuilder.append(": ");
181 requestHeadersBuilder.append(userAgent);
184 // Set the `x-requested-with` header property.
185 httpUrlConnection.setRequestProperty("x-requested-with", "");
187 // Add the `x-requested-with` header to the string builder and format the text.
188 requestHeadersBuilder.append(System.getProperty("line.separator"));
189 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
190 requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
191 } else { // Older versions not so much.
192 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
193 requestHeadersBuilder.append("x-requested-with");
194 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
195 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
197 requestHeadersBuilder.append(": ");
200 // Set the `Sec-Fetch-Site` header property.
201 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
203 // Add the `Sec-Fetch-Site` header to the string builder and format the text.
204 requestHeadersBuilder.append(System.getProperty("line.separator"));
205 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
206 requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
207 } else { // Older versions not so much.
208 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
209 requestHeadersBuilder.append("Sec-Fetch-Site");
210 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
211 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
213 requestHeadersBuilder.append(": none");
216 // Set the `Sec-Fetch-Mode` header property.
217 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
219 // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
220 requestHeadersBuilder.append(System.getProperty("line.separator"));
221 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
222 requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
223 } else { // Older versions not so much.
224 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
225 requestHeadersBuilder.append("Sec-Fetch-Mode");
226 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
227 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
229 requestHeadersBuilder.append(": navigate");
232 // Set the `Sec-Fetch-User` header property.
233 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
235 // Add the `Sec-Fetch-User` header to the string builder and format the text.
236 requestHeadersBuilder.append(System.getProperty("line.separator"));
237 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
238 requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
239 } else { // Older versions not so much.
240 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
241 requestHeadersBuilder.append("Sec-Fetch-User");
242 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
243 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
245 requestHeadersBuilder.append(": ?1");
248 // Set the `Accept` header property.
249 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");
251 // Add the `Accept` 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("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
255 } else { // Older versions not so much.
256 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
257 requestHeadersBuilder.append("Accept");
258 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
259 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
261 requestHeadersBuilder.append(": ");
262 requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
265 // Set the `Accept-Language` header property.
266 httpUrlConnection.setRequestProperty("Accept-Language", localeString);
268 // Add the `Accept-Language` header to the string builder and format the text.
269 requestHeadersBuilder.append(System.getProperty("line.separator"));
270 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
271 requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
272 } else { // Older versions not so much.
273 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
274 requestHeadersBuilder.append("Accept-Language");
275 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
276 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
278 requestHeadersBuilder.append(": ");
279 requestHeadersBuilder.append(localeString);
282 // Get the cookies for the current domain.
283 String cookiesString = CookieManager.getInstance().getCookie(url.toString());
285 // Only process the cookies if they are not null.
286 if (cookiesString != null) {
287 // Add the cookies to the header property.
288 httpUrlConnection.setRequestProperty("Cookie", cookiesString);
290 // Add the cookie header to the string builder and format the text.
291 requestHeadersBuilder.append(System.getProperty("line.separator"));
292 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
293 requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
294 } else { // Older versions not so much.
295 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
296 requestHeadersBuilder.append("Cookie");
297 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
298 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
300 requestHeadersBuilder.append(": ");
301 requestHeadersBuilder.append(cookiesString);
305 // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default. If the property is manually set, than `HttpUrlConnection` does not process the decoding.
306 // Add the `Accept-Encoding` header to the string builder and format the text.
307 requestHeadersBuilder.append(System.getProperty("line.separator"));
308 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
309 requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
310 } else { // Older versions not so much.
311 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
312 requestHeadersBuilder.append("Accept-Encoding");
313 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
314 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
316 requestHeadersBuilder.append(": gzip");
319 // 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.
321 // Initialize the string builders.
322 responseMessageBuilder = new SpannableStringBuilder();
323 responseHeadersBuilder = new SpannableStringBuilder();
325 // Get the response code, which causes the connection to the server to be made.
326 int responseCode = httpUrlConnection.getResponseCode();
328 // Populate the response message string builder.
329 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
330 responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
331 } else { // Older versions not so much.
332 responseMessageBuilder.append(String.valueOf(responseCode));
333 int newLength = responseMessageBuilder.length();
334 responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
336 responseMessageBuilder.append(": ");
337 responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
339 // Initialize the iteration variable.
342 // Iterate through the received header fields.
343 while (httpUrlConnection.getHeaderField(i) != null) {
344 // Add a new line if there is already information in the string builder.
346 responseHeadersBuilder.append(System.getProperty("line.separator"));
349 // Add the header to the string builder and format the text.
350 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
351 responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
352 } else { // Older versions not so much.
353 int oldLength = responseHeadersBuilder.length();
354 responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
355 int newLength = responseHeadersBuilder.length();
356 responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
358 responseHeadersBuilder.append(": ");
359 responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
361 // Increment the iteration variable.
365 // Instantiate an input stream for the response body.
366 InputStream inputStream;
368 // Get the correct input stream based on the response code.
369 if (responseCode == 404) { // Get the error stream.
370 inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
371 } else { // Get the response body stream.
372 inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
375 // Initialize the byte array output stream and the conversion buffer byte array.
376 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
377 byte[] conversionBufferByteArray = new byte[1024];
379 // Define the buffer length variable.
383 // 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.
384 while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) { // Proceed while the amount of data stored in the buffer is > 0.
385 // Write the contents of the conversion buffer to the byte array output stream.
386 byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
388 } catch (IOException exception) {
389 // Return the error message.
390 webViewSource.returnError(exception.toString());
393 // Close the input stream.
396 // Populate the response body string with the contents of the byte array output stream.
397 responseBodyBuilder.append(byteArrayOutputStream.toString());
399 // Disconnect HTTP URL connection.
400 httpUrlConnection.disconnect();
402 } catch (Exception exception) {
403 // Return the error message.
404 webViewSource.returnError(exception.toString());
408 // Return the response body string as the result.
409 return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};