2 * Copyright © 2017-2022 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
6 * Privacy Browser Android 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 Android 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 Android. 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.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;
43 import java.security.SecureRandom;
44 import java.security.cert.X509Certificate;
46 import javax.net.ssl.HostnameVerifier;
47 import javax.net.ssl.HttpsURLConnection;
48 import javax.net.ssl.SSLContext;
49 import javax.net.ssl.SSLSocketFactory;
50 import javax.net.ssl.TrustManager;
51 import javax.net.ssl.X509TrustManager;
53 public class GetSourceBackgroundTask {
54 public SpannableStringBuilder[] acquire(String urlString, String userAgent, String localeString, Proxy proxy, ContentResolver contentResolver, WebViewSource webViewSource, boolean ignoreSslErrors) {
55 // Initialize the spannable string builders.
56 SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
57 SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
58 SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
59 SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
61 if (urlString.startsWith("content://")) { // This is a content URL.
62 // Attempt to read the content data. Return an error if this fails.
64 // Get a URI for the content URL.
65 Uri contentUri = Uri.parse(urlString);
67 // Get a cursor with metadata about the content URL.
68 Cursor contentCursor = contentResolver.query(contentUri, null, null, null, null);
70 // Move the content cursor to the first row.
71 contentCursor.moveToFirst();
73 for (int i = 0; i < contentCursor.getColumnCount(); i++) {
74 // Add a new line if this is not the first entry.
76 responseHeadersBuilder.append(System.getProperty("line.separator"));
79 // Add each header to the string builder.
80 responseHeadersBuilder.append(contentCursor.getColumnName(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
81 responseHeadersBuilder.append(": ");
82 responseHeadersBuilder.append(contentCursor.getString(i));
85 // Close the content cursor.
86 contentCursor.close();
88 // Create a buffered string reader for the content data.
89 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(contentResolver.openInputStream(contentUri)));
91 // Get the data from the buffered reader one line at a time.
92 for (String contentLineString; ((contentLineString = bufferedReader.readLine()) != null);) {
93 // Add the line to the response body builder.
94 responseBodyBuilder.append(contentLineString);
97 responseBodyBuilder.append("\n");
99 } catch (Exception exception) {
100 // Return the error message.
101 webViewSource.returnError(exception.toString());
103 } else { // This is not a content URL.
104 // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
106 // Get the current URL from the main activity.
107 URL url = new URL(urlString);
109 // Open a connection to the URL. No data is actually sent at this point.
110 HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
112 // Set the `Host` header property.
113 httpUrlConnection.setRequestProperty("Host", url.getHost());
115 // Add the `Host` header to the string builder and format the text.
116 requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
117 requestHeadersBuilder.append(": ");
118 requestHeadersBuilder.append(url.getHost());
121 // Set the `Connection` header property.
122 httpUrlConnection.setRequestProperty("Connection", "keep-alive");
124 // Add the `Connection` header to the string builder and format the text.
125 requestHeadersBuilder.append(System.getProperty("line.separator"));
126 requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
127 requestHeadersBuilder.append(": keep-alive");
130 // Set the `Upgrade-Insecure-Requests` header property.
131 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
133 // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
134 requestHeadersBuilder.append(System.getProperty("line.separator"));
135 requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
136 requestHeadersBuilder.append(": 1");
139 // Set the `User-Agent` header property.
140 httpUrlConnection.setRequestProperty("User-Agent", userAgent);
142 // Add the `User-Agent` header to the string builder and format the text.
143 requestHeadersBuilder.append(System.getProperty("line.separator"));
144 requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
145 requestHeadersBuilder.append(": ");
146 requestHeadersBuilder.append(userAgent);
149 // Set the `x-requested-with` header property.
150 httpUrlConnection.setRequestProperty("x-requested-with", "");
152 // Add the `x-requested-with` header to the string builder and format the text.
153 requestHeadersBuilder.append(System.getProperty("line.separator"));
154 requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
155 requestHeadersBuilder.append(": ");
158 // Set the `Sec-Fetch-Site` header property.
159 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
161 // Add the `Sec-Fetch-Site` header to the string builder and format the text.
162 requestHeadersBuilder.append(System.getProperty("line.separator"));
163 requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
164 requestHeadersBuilder.append(": none");
167 // Set the `Sec-Fetch-Mode` header property.
168 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
170 // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
171 requestHeadersBuilder.append(System.getProperty("line.separator"));
172 requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
173 requestHeadersBuilder.append(": navigate");
176 // Set the `Sec-Fetch-User` header property.
177 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
179 // Add the `Sec-Fetch-User` header to the string builder and format the text.
180 requestHeadersBuilder.append(System.getProperty("line.separator"));
181 requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
182 requestHeadersBuilder.append(": ?1");
185 // Set the `Accept` header property.
186 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");
188 // Add the `Accept` header to the string builder and format the text.
189 requestHeadersBuilder.append(System.getProperty("line.separator"));
190 requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
191 requestHeadersBuilder.append(": ");
192 requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
195 // Set the `Accept-Language` header property.
196 httpUrlConnection.setRequestProperty("Accept-Language", localeString);
198 // Add the `Accept-Language` header to the string builder and format the text.
199 requestHeadersBuilder.append(System.getProperty("line.separator"));
200 requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
201 requestHeadersBuilder.append(": ");
202 requestHeadersBuilder.append(localeString);
205 // Get the cookies for the current domain.
206 String cookiesString = CookieManager.getInstance().getCookie(url.toString());
208 // Only process the cookies if they are not null.
209 if (cookiesString != null) {
210 // Add the cookies to the header property.
211 httpUrlConnection.setRequestProperty("Cookie", cookiesString);
213 // Add the cookie header to the string builder and format the text.
214 requestHeadersBuilder.append(System.getProperty("line.separator"));
215 requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
216 requestHeadersBuilder.append(": ");
217 requestHeadersBuilder.append(cookiesString);
221 // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default. If the property is manually set, than `HttpUrlConnection` does not process the decoding.
222 // Add the `Accept-Encoding` header to the string builder and format the text.
223 requestHeadersBuilder.append(System.getProperty("line.separator"));
224 requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
225 requestHeadersBuilder.append(": gzip");
227 // Ignore SSL errors if requested.
228 if (ignoreSslErrors){
229 // Create a new host name verifier.
230 HostnameVerifier hostnameVerifier = (hostname, sslSession) -> {
231 // Allow all host names.
235 // Create a new trust manager. Lint wants to warn us that it is hard to securely implement an X509 trust manager.
236 // But the point of this trust manager is that it should accept all certificates no matter what, so that isn't an issue in our case.
237 @SuppressLint("CustomX509TrustManager") TrustManager[] trustManager = new TrustManager[] {
238 new X509TrustManager() {
239 @SuppressLint("TrustAllX509TrustManager")
241 public void checkClientTrusted(X509Certificate[] chain, String authType) {
242 // Do nothing, which trusts all client certificates.
245 @SuppressLint("TrustAllX509TrustManager")
247 public void checkServerTrusted(X509Certificate[] chain, String authType) {
248 // Do nothing, which trusts all server certificates.
252 public X509Certificate[] getAcceptedIssuers() {
258 // Get an SSL context. `TLS` provides a base instance available from API 1. <https://developer.android.com/reference/javax/net/ssl/SSLContext>
259 SSLContext sslContext = SSLContext.getInstance("TLS");
261 // Initialize the SSL context with the blank trust manager.
262 sslContext.init(null, trustManager, new SecureRandom());
264 // Get the SSL socket factory with the blank trust manager.
265 SSLSocketFactory socketFactory = sslContext.getSocketFactory();
267 // Set the HTTPS URL Connection to use the blank host name verifier.
268 ((HttpsURLConnection) httpUrlConnection).setHostnameVerifier(hostnameVerifier);
270 // Set the HTTPS URL connection to use the socket factory with the blank trust manager.
271 ((HttpsURLConnection) httpUrlConnection).setSSLSocketFactory(socketFactory);
274 // 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.
276 // Get the response code, which causes the connection to the server to be made.
277 int responseCode = httpUrlConnection.getResponseCode();
279 // Populate the response message string builder.
280 responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
281 responseMessageBuilder.append(": ");
282 responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
284 // Initialize the iteration variable.
287 // Iterate through the received header fields.
288 while (httpUrlConnection.getHeaderField(i) != null) {
289 // Add a new line if there is already information in the string builder.
291 responseHeadersBuilder.append(System.getProperty("line.separator"));
294 // Add the header to the string builder and format the text.
295 responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
296 responseHeadersBuilder.append(": ");
297 responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
299 // Increment the iteration variable.
303 // Instantiate an input stream for the response body.
304 InputStream inputStream;
306 // Get the correct input stream based on the response code.
307 if (responseCode == 404) { // Get the error stream.
308 inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
309 } else { // Get the response body stream.
310 inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
313 // Initialize the byte array output stream and the conversion buffer byte array.
314 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
315 byte[] conversionBufferByteArray = new byte[1024];
317 // Define the buffer length variable.
321 // 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.
322 while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) { // Proceed while the amount of data stored in the buffer is > 0.
323 // Write the contents of the conversion buffer to the byte array output stream.
324 byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
326 } catch (IOException exception) {
327 // Return the error message.
328 webViewSource.returnError(exception.toString());
331 // Close the input stream.
334 // Populate the response body string with the contents of the byte array output stream.
335 responseBodyBuilder.append(byteArrayOutputStream.toString());
337 // Disconnect HTTP URL connection.
338 httpUrlConnection.disconnect();
340 } catch (Exception exception) {
341 // Return the error message.
342 webViewSource.returnError(exception.toString());
346 // Return the spannable string builders.
347 return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};