Bump the minimum API to 23. https://redmine.stoutner.com/issues/793
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / backgroundtasks / GetSourceBackgroundTask.java
1 /*
2  * Copyright © 2017-2021 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser.backgroundtasks;
21
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;
32
33 import com.stoutner.privacybrowser.viewmodels.WebViewSource;
34
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;
43 import java.net.URL;
44 import java.security.SecureRandom;
45 import java.security.cert.X509Certificate;
46
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;
53
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();
61
62         if (urlString.startsWith("content://")) {  // This is a content URL.
63             // Attempt to read the content data.  Return an error if this fails.
64             try {
65                 // Get a URI for the content URL.
66                 Uri contentUri = Uri.parse(urlString);
67
68                 // Get a cursor with metadata about the content URL.
69                 Cursor contentCursor = contentResolver.query(contentUri, null, null, null, null);
70
71                 // Move the content cursor to the first row.
72                 contentCursor.moveToFirst();
73
74                 for (int i = 0; i < contentCursor.getColumnCount(); i++) {
75                     // Add a new line if this is not the first entry.
76                     if (i > 0) {
77                         responseHeadersBuilder.append(System.getProperty("line.separator"));
78                     }
79
80                     // Add each header to the string builder.
81                     responseHeadersBuilder.append(contentCursor.getColumnName(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
82                     responseHeadersBuilder.append(":  ");
83                     responseHeadersBuilder.append(contentCursor.getString(i));
84                 }
85
86                 // Close the content cursor.
87                 contentCursor.close();
88
89                 // Create a buffered string reader for the content data.
90                 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(contentResolver.openInputStream(contentUri)));
91
92                 // Get the data from the buffered reader one line at a time.
93                 for (String contentLineString; ((contentLineString = bufferedReader.readLine()) != null);) {
94                     // Add the line to the response body builder.
95                     responseBodyBuilder.append(contentLineString);
96
97                     // Append a new line.
98                     responseBodyBuilder.append("\n");
99                 }
100             } catch (Exception exception) {
101                 // Return the error message.
102                 webViewSource.returnError(exception.toString());
103             }
104         } else {  // This is not a content URL.
105             // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
106             try {
107                 // Get the current URL from the main activity.
108                 URL url = new URL(urlString);
109
110                 // Open a connection to the URL.  No data is actually sent at this point.
111                 HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
112
113                 // Set the `Host` header property.
114                 httpUrlConnection.setRequestProperty("Host", url.getHost());
115
116                 // Add the `Host` header to the string builder and format the text.
117                 requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
118                 requestHeadersBuilder.append(":  ");
119                 requestHeadersBuilder.append(url.getHost());
120
121
122                 // Set the `Connection` header property.
123                 httpUrlConnection.setRequestProperty("Connection", "keep-alive");
124
125                 // Add the `Connection` header to the string builder and format the text.
126                 requestHeadersBuilder.append(System.getProperty("line.separator"));
127                 requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
128                 requestHeadersBuilder.append(":  keep-alive");
129
130
131                 // Set the `Upgrade-Insecure-Requests` header property.
132                 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
133
134                 // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
135                 requestHeadersBuilder.append(System.getProperty("line.separator"));
136                 requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
137                 requestHeadersBuilder.append(":  1");
138
139
140                 // Set the `User-Agent` header property.
141                 httpUrlConnection.setRequestProperty("User-Agent", userAgent);
142
143                 // Add the `User-Agent` header to the string builder and format the text.
144                 requestHeadersBuilder.append(System.getProperty("line.separator"));
145                 requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
146                 requestHeadersBuilder.append(":  ");
147                 requestHeadersBuilder.append(userAgent);
148
149
150                 // Set the `x-requested-with` header property.
151                 httpUrlConnection.setRequestProperty("x-requested-with", "");
152
153                 // Add the `x-requested-with` header to the string builder and format the text.
154                 requestHeadersBuilder.append(System.getProperty("line.separator"));
155                 requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
156                 requestHeadersBuilder.append(":  ");
157
158
159                 // Set the `Sec-Fetch-Site` header property.
160                 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
161
162                 // Add the `Sec-Fetch-Site` header to the string builder and format the text.
163                 requestHeadersBuilder.append(System.getProperty("line.separator"));
164                 requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
165                 requestHeadersBuilder.append(":  none");
166
167
168                 // Set the `Sec-Fetch-Mode` header property.
169                 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
170
171                 // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
172                 requestHeadersBuilder.append(System.getProperty("line.separator"));
173                 requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
174                 requestHeadersBuilder.append(":  navigate");
175
176
177                 // Set the `Sec-Fetch-User` header property.
178                 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
179
180                 // Add the `Sec-Fetch-User` header to the string builder and format the text.
181                 requestHeadersBuilder.append(System.getProperty("line.separator"));
182                 requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
183                 requestHeadersBuilder.append(":  ?1");
184
185
186                 // Set the `Accept` header property.
187                 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
189                 // Add the `Accept` header to the string builder and format the text.
190                 requestHeadersBuilder.append(System.getProperty("line.separator"));
191                 requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
192                 requestHeadersBuilder.append(":  ");
193                 requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
194
195
196                 // Set the `Accept-Language` header property.
197                 httpUrlConnection.setRequestProperty("Accept-Language", localeString);
198
199                 // Add the `Accept-Language` header to the string builder and format the text.
200                 requestHeadersBuilder.append(System.getProperty("line.separator"));
201                 requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
202                 requestHeadersBuilder.append(":  ");
203                 requestHeadersBuilder.append(localeString);
204
205
206                 // Get the cookies for the current domain.
207                 String cookiesString = CookieManager.getInstance().getCookie(url.toString());
208
209                 // Only process the cookies if they are not null.
210                 if (cookiesString != null) {
211                     // Add the cookies to the header property.
212                     httpUrlConnection.setRequestProperty("Cookie", cookiesString);
213
214                     // Add the cookie header to the string builder and format the text.
215                     requestHeadersBuilder.append(System.getProperty("line.separator"));
216                     requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
217                     requestHeadersBuilder.append(":  ");
218                     requestHeadersBuilder.append(cookiesString);
219                 }
220
221
222                 // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
223                 // Add the `Accept-Encoding` header to the string builder and format the text.
224                 requestHeadersBuilder.append(System.getProperty("line.separator"));
225                 requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
226                 requestHeadersBuilder.append(":  gzip");
227
228                 // Ignore SSL errors if requested.
229                 if (ignoreSslErrors){
230                     // Create a new host name verifier.
231                     HostnameVerifier hostnameVerifier = (hostname, sslSession) -> {
232                         // Allow all host names.
233                         return true;
234                     };
235
236                     // Create a new trust manager.
237                     TrustManager[] trustManager = new TrustManager[] {
238                             new X509TrustManager() {
239                                 @SuppressLint("TrustAllX509TrustManager")
240                                 @Override
241                                 public void checkClientTrusted(X509Certificate[] chain, String authType) {
242                                     // Do nothing, which trusts all client certificates.
243                                 }
244
245                                 @SuppressLint("TrustAllX509TrustManager")
246                                 @Override
247                                 public void checkServerTrusted(X509Certificate[] chain, String authType) {
248                                     // Do nothing, which trusts all server certificates.
249                                 }
250
251                                 @Override
252                                 public X509Certificate[] getAcceptedIssuers() {
253                                     return null;
254                                 }
255                             }
256                     };
257
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");
260
261                     // Initialize the SSL context with the blank trust manager.
262                     sslContext.init(null, trustManager, new SecureRandom());
263
264                     // Get the SSL socket factory with the blank trust manager.
265                     SSLSocketFactory socketFactory = sslContext.getSocketFactory();
266
267                     // Set the HTTPS URL Connection to use the blank host name verifier.
268                     ((HttpsURLConnection) httpUrlConnection).setHostnameVerifier(hostnameVerifier);
269
270                     // Set the HTTPS URL connection to use the socket factory with the blank trust manager.
271                     ((HttpsURLConnection) httpUrlConnection).setSSLSocketFactory(socketFactory);
272                 }
273
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.
275                 try {
276                     // Get the response code, which causes the connection to the server to be made.
277                     int responseCode = httpUrlConnection.getResponseCode();
278
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());
283
284                     // Initialize the iteration variable.
285                     int i = 0;
286
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.
290                         if (i > 0) {
291                             responseHeadersBuilder.append(System.getProperty("line.separator"));
292                         }
293
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));
298
299                         // Increment the iteration variable.
300                         i++;
301                     }
302
303                     // Instantiate an input stream for the response body.
304                     InputStream inputStream;
305
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());
311                     }
312
313                     // Initialize the byte array output stream and the conversion buffer byte array.
314                     ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
315                     byte[] conversionBufferByteArray = new byte[1024];
316
317                     // Define the buffer length variable.
318                     int bufferLength;
319
320                     try {
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);
325                         }
326                     } catch (IOException exception) {
327                         // Return the error message.
328                         webViewSource.returnError(exception.toString());
329                     }
330
331                     // Close the input stream.
332                     inputStream.close();
333
334                     // Populate the response body string with the contents of the byte array output stream.
335                     responseBodyBuilder.append(byteArrayOutputStream.toString());
336                 } finally {
337                     // Disconnect HTTP URL connection.
338                     httpUrlConnection.disconnect();
339                 }
340             } catch (Exception exception) {
341                 // Return the error message.
342                 webViewSource.returnError(exception.toString());
343             }
344         }
345
346         // Return the spannable string builders.
347         return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
348     }
349 }