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.annotation.SuppressLint;
23 import android.content.ContentResolver;
24 import android.database.Cursor;
25 import android.graphics.Typeface;
26 import android.net.Uri;
27 import android.os.Build;
28 import android.text.SpannableStringBuilder;
29 import android.text.Spanned;
30 import android.text.style.StyleSpan;
31 import android.webkit.CookieManager;
33 import com.stoutner.privacybrowser.viewmodels.WebViewSource;
35 import java.io.BufferedInputStream;
36 import java.io.BufferedReader;
37 import java.io.ByteArrayOutputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.InputStreamReader;
41 import java.net.HttpURLConnection;
42 import java.net.Proxy;
44 import java.security.SecureRandom;
45 import java.security.cert.X509Certificate;
47 import javax.net.ssl.HostnameVerifier;
48 import javax.net.ssl.HttpsURLConnection;
49 import javax.net.ssl.SSLContext;
50 import javax.net.ssl.SSLSocketFactory;
51 import javax.net.ssl.TrustManager;
52 import javax.net.ssl.X509TrustManager;
54 public class GetSourceBackgroundTask {
55 public SpannableStringBuilder[] acquire(String urlString, String userAgent, String localeString, Proxy proxy, ContentResolver contentResolver, WebViewSource webViewSource, boolean ignoreSslErrors) {
56 // Initialize the spannable string builders.
57 SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
58 SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
59 SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
60 SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
62 if (urlString.startsWith("content://")) { // This is a content URL.
63 // Attempt to read the content data. Return an error if this fails.
65 // Get a URI for the content URL.
66 Uri contentUri = Uri.parse(urlString);
68 // Define the variables necessary to build the response headers.
69 int oldResponseHeadersBuilderLength;
70 int newResponseHeadersBuilderLength;
72 // Get a cursor with metadata about the content URL.
73 Cursor contentCursor = contentResolver.query(contentUri, null, null, null, null);
75 // Move the content cursor to the first row.
76 contentCursor.moveToFirst();
78 for (int i = 0; i < contentCursor.getColumnCount(); i++) {
79 // Add a new line if this is not the first entry.
81 responseHeadersBuilder.append(System.getProperty("line.separator"));
84 // Add each header to the string builder.
85 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
86 responseHeadersBuilder.append(contentCursor.getColumnName(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
87 } else { // Older versions are not so much.
88 oldResponseHeadersBuilderLength = responseHeadersBuilder.length();
89 responseHeadersBuilder.append(contentCursor.getColumnName(i));
90 newResponseHeadersBuilderLength = responseHeadersBuilder.length();
91 responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldResponseHeadersBuilderLength, newResponseHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
93 responseHeadersBuilder.append(": ");
94 responseHeadersBuilder.append(contentCursor.getString(i));
97 // Close the content cursor.
98 contentCursor.close();
100 // Create a buffered string reader for the content data.
101 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(contentResolver.openInputStream(contentUri)));
103 // Get the data from the buffered reader one line at a time.
104 for (String contentLineString; ((contentLineString = bufferedReader.readLine()) != null);) {
105 // Add the line to the response body builder.
106 responseBodyBuilder.append(contentLineString);
108 // Append a new line.
109 responseBodyBuilder.append("\n");
111 } catch (Exception exception) {
112 // Return the error message.
113 webViewSource.returnError(exception.toString());
115 } else { // This is not a content URL.
116 // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
118 // Get the current URL from the main activity.
119 URL url = new URL(urlString);
121 // Open a connection to the URL. No data is actually sent at this point.
122 HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
124 // Define the variables necessary to build the request headers.
125 int oldRequestHeadersBuilderLength;
126 int newRequestHeadersBuilderLength;
129 // Set the `Host` header property.
130 httpUrlConnection.setRequestProperty("Host", url.getHost());
132 // Add the `Host` header to the string builder and format the text.
133 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
134 requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
135 } else { // Older versions not so much.
136 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
137 requestHeadersBuilder.append("Host");
138 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
139 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
141 requestHeadersBuilder.append(": ");
142 requestHeadersBuilder.append(url.getHost());
145 // Set the `Connection` header property.
146 httpUrlConnection.setRequestProperty("Connection", "keep-alive");
148 // Add the `Connection` header to the string builder and format the text.
149 requestHeadersBuilder.append(System.getProperty("line.separator"));
150 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
151 requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
152 } else { // Older versions not so much.
153 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
154 requestHeadersBuilder.append("Connection");
155 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
156 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
158 requestHeadersBuilder.append(": keep-alive");
161 // Set the `Upgrade-Insecure-Requests` header property.
162 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
164 // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
165 requestHeadersBuilder.append(System.getProperty("line.separator"));
166 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
167 requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
168 } else { // Older versions not so much.
169 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
170 requestHeadersBuilder.append("Upgrade-Insecure_Requests");
171 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
172 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
174 requestHeadersBuilder.append(": 1");
177 // Set the `User-Agent` header property.
178 httpUrlConnection.setRequestProperty("User-Agent", userAgent);
180 // Add the `User-Agent` header to the string builder and format the text.
181 requestHeadersBuilder.append(System.getProperty("line.separator"));
182 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
183 requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
184 } else { // Older versions not so much.
185 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
186 requestHeadersBuilder.append("User-Agent");
187 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
188 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
190 requestHeadersBuilder.append(": ");
191 requestHeadersBuilder.append(userAgent);
194 // Set the `x-requested-with` header property.
195 httpUrlConnection.setRequestProperty("x-requested-with", "");
197 // Add the `x-requested-with` header to the string builder and format the text.
198 requestHeadersBuilder.append(System.getProperty("line.separator"));
199 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
200 requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
201 } else { // Older versions not so much.
202 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
203 requestHeadersBuilder.append("x-requested-with");
204 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
205 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
207 requestHeadersBuilder.append(": ");
210 // Set the `Sec-Fetch-Site` header property.
211 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
213 // Add the `Sec-Fetch-Site` header to the string builder and format the text.
214 requestHeadersBuilder.append(System.getProperty("line.separator"));
215 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
216 requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
217 } else { // Older versions not so much.
218 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
219 requestHeadersBuilder.append("Sec-Fetch-Site");
220 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
221 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
223 requestHeadersBuilder.append(": none");
226 // Set the `Sec-Fetch-Mode` header property.
227 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
229 // Add the `Sec-Fetch-Mode` 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("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
233 } else { // Older versions not so much.
234 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
235 requestHeadersBuilder.append("Sec-Fetch-Mode");
236 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
237 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
239 requestHeadersBuilder.append(": navigate");
242 // Set the `Sec-Fetch-User` header property.
243 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
245 // Add the `Sec-Fetch-User` header to the string builder and format the text.
246 requestHeadersBuilder.append(System.getProperty("line.separator"));
247 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
248 requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
249 } else { // Older versions not so much.
250 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
251 requestHeadersBuilder.append("Sec-Fetch-User");
252 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
253 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
255 requestHeadersBuilder.append(": ?1");
258 // Set the `Accept` header property.
259 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");
261 // Add the `Accept` header to the string builder and format the text.
262 requestHeadersBuilder.append(System.getProperty("line.separator"));
263 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
264 requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
265 } else { // Older versions not so much.
266 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
267 requestHeadersBuilder.append("Accept");
268 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
269 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
271 requestHeadersBuilder.append(": ");
272 requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
275 // Set the `Accept-Language` header property.
276 httpUrlConnection.setRequestProperty("Accept-Language", localeString);
278 // Add the `Accept-Language` header to the string builder and format the text.
279 requestHeadersBuilder.append(System.getProperty("line.separator"));
280 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
281 requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
282 } else { // Older versions not so much.
283 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
284 requestHeadersBuilder.append("Accept-Language");
285 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
286 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
288 requestHeadersBuilder.append(": ");
289 requestHeadersBuilder.append(localeString);
292 // Get the cookies for the current domain.
293 String cookiesString = CookieManager.getInstance().getCookie(url.toString());
295 // Only process the cookies if they are not null.
296 if (cookiesString != null) {
297 // Add the cookies to the header property.
298 httpUrlConnection.setRequestProperty("Cookie", cookiesString);
300 // Add the cookie header to the string builder and format the text.
301 requestHeadersBuilder.append(System.getProperty("line.separator"));
302 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
303 requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
304 } else { // Older versions not so much.
305 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
306 requestHeadersBuilder.append("Cookie");
307 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
308 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
310 requestHeadersBuilder.append(": ");
311 requestHeadersBuilder.append(cookiesString);
315 // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default. If the property is manually set, than `HttpUrlConnection` does not process the decoding.
316 // Add the `Accept-Encoding` header to the string builder and format the text.
317 requestHeadersBuilder.append(System.getProperty("line.separator"));
318 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
319 requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
320 } else { // Older versions not so much.
321 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
322 requestHeadersBuilder.append("Accept-Encoding");
323 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
324 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
326 requestHeadersBuilder.append(": gzip");
328 // Ignore SSL errors if requested.
329 if (ignoreSslErrors){
330 // Create a new host name verifier.
331 HostnameVerifier hostnameVerifier = (hostname, sslSession) -> {
332 // Allow all host names.
336 // Create a new trust manager.
337 TrustManager[] trustManager = new TrustManager[] {
338 new X509TrustManager() {
339 @SuppressLint("TrustAllX509TrustManager")
341 public void checkClientTrusted(X509Certificate[] chain, String authType) {
342 // Do nothing, which trusts all client certificates.
345 @SuppressLint("TrustAllX509TrustManager")
347 public void checkServerTrusted(X509Certificate[] chain, String authType) {
348 // Do nothing, which trusts all server certificates.
352 public X509Certificate[] getAcceptedIssuers() {
358 // Get an SSL context. `TLS` provides a base instance available from API 1. <https://developer.android.com/reference/javax/net/ssl/SSLContext>
359 SSLContext sslContext = SSLContext.getInstance("TLS");
361 // Initialize the SSL context with the blank trust manager.
362 sslContext.init(null, trustManager, new SecureRandom());
364 // Get the SSL socket factory with the blank trust manager.
365 SSLSocketFactory socketFactory = sslContext.getSocketFactory();
367 // Set the HTTPS URL Connection to use the blank host name verifier.
368 ((HttpsURLConnection) httpUrlConnection).setHostnameVerifier(hostnameVerifier);
370 // Set the HTTPS URL connection to use the socket factory with the blank trust manager.
371 ((HttpsURLConnection) httpUrlConnection).setSSLSocketFactory(socketFactory);
374 // 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.
376 // Get the response code, which causes the connection to the server to be made.
377 int responseCode = httpUrlConnection.getResponseCode();
379 // Populate the response message string builder.
380 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
381 responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
382 } else { // Older versions not so much.
383 responseMessageBuilder.append(String.valueOf(responseCode));
384 int newLength = responseMessageBuilder.length();
385 responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
387 responseMessageBuilder.append(": ");
388 responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
390 // Initialize the iteration variable.
393 // Iterate through the received header fields.
394 while (httpUrlConnection.getHeaderField(i) != null) {
395 // Add a new line if there is already information in the string builder.
397 responseHeadersBuilder.append(System.getProperty("line.separator"));
400 // Add the header to the string builder and format the text.
401 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
402 responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
403 } else { // Older versions not so much.
404 int oldLength = responseHeadersBuilder.length();
405 responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
406 int newLength = responseHeadersBuilder.length();
407 responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
409 responseHeadersBuilder.append(": ");
410 responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
412 // Increment the iteration variable.
416 // Instantiate an input stream for the response body.
417 InputStream inputStream;
419 // Get the correct input stream based on the response code.
420 if (responseCode == 404) { // Get the error stream.
421 inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
422 } else { // Get the response body stream.
423 inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
426 // Initialize the byte array output stream and the conversion buffer byte array.
427 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
428 byte[] conversionBufferByteArray = new byte[1024];
430 // Define the buffer length variable.
434 // 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.
435 while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) { // Proceed while the amount of data stored in the buffer is > 0.
436 // Write the contents of the conversion buffer to the byte array output stream.
437 byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
439 } catch (IOException exception) {
440 // Return the error message.
441 webViewSource.returnError(exception.toString());
444 // Close the input stream.
447 // Populate the response body string with the contents of the byte array output stream.
448 responseBodyBuilder.append(byteArrayOutputStream.toString());
450 // Disconnect HTTP URL connection.
451 httpUrlConnection.disconnect();
453 } catch (Exception exception) {
454 // Return the error message.
455 webViewSource.returnError(exception.toString());
459 // Return the spannable string builders.
460 return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};