]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/backgroundtasks/GetSourceBackgroundTask.java
Release 3.12.
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / backgroundtasks / GetSourceBackgroundTask.java
1 /*
2  * Copyright © 2017-2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
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.
10  *
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.
15  *
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/>.
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.text.SpannableStringBuilder;
28 import android.text.Spanned;
29 import android.text.style.StyleSpan;
30 import android.webkit.CookieManager;
31
32 import com.stoutner.privacybrowser.viewmodels.WebViewSource;
33
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;
42 import java.net.URL;
43 import java.security.SecureRandom;
44 import java.security.cert.X509Certificate;
45
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;
52
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();
60
61         if (urlString.startsWith("content://")) {  // This is a content URL.
62             // Attempt to read the content data.  Return an error if this fails.
63             try {
64                 // Get a URI for the content URL.
65                 Uri contentUri = Uri.parse(urlString);
66
67                 // Get a cursor with metadata about the content URL.
68                 Cursor contentCursor = contentResolver.query(contentUri, null, null, null, null);
69
70                 // Move the content cursor to the first row.
71                 contentCursor.moveToFirst();
72
73                 for (int i = 0; i < contentCursor.getColumnCount(); i++) {
74                     // Add a new line if this is not the first entry.
75                     if (i > 0) {
76                         responseHeadersBuilder.append(System.getProperty("line.separator"));
77                     }
78
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));
83                 }
84
85                 // Close the content cursor.
86                 contentCursor.close();
87
88                 // Create a buffered string reader for the content data.
89                 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(contentResolver.openInputStream(contentUri)));
90
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);
95
96                     // Append a new line.
97                     responseBodyBuilder.append("\n");
98                 }
99             } catch (Exception exception) {
100                 // Return the error message.
101                 webViewSource.returnError(exception.toString());
102             }
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`.
105             try {
106                 // Get the current URL from the main activity.
107                 URL url = new URL(urlString);
108
109                 // Open a connection to the URL.  No data is actually sent at this point.
110                 HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
111
112                 // Set the `Host` header property.
113                 httpUrlConnection.setRequestProperty("Host", url.getHost());
114
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());
119
120
121                 // Set the `Connection` header property.
122                 httpUrlConnection.setRequestProperty("Connection", "keep-alive");
123
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");
128
129
130                 // Set the `Upgrade-Insecure-Requests` header property.
131                 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
132
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");
137
138
139                 // Set the `User-Agent` header property.
140                 httpUrlConnection.setRequestProperty("User-Agent", userAgent);
141
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);
147
148
149                 // Set the `Sec-Fetch-Site` header property.
150                 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
151
152                 // Add the `Sec-Fetch-Site` header to the string builder and format the text.
153                 requestHeadersBuilder.append(System.getProperty("line.separator"));
154                 requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
155                 requestHeadersBuilder.append(":  none");
156
157
158                 // Set the `Sec-Fetch-Mode` header property.
159                 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
160
161                 // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
162                 requestHeadersBuilder.append(System.getProperty("line.separator"));
163                 requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
164                 requestHeadersBuilder.append(":  navigate");
165
166
167                 // Set the `Sec-Fetch-User` header property.
168                 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
169
170                 // Add the `Sec-Fetch-User` header to the string builder and format the text.
171                 requestHeadersBuilder.append(System.getProperty("line.separator"));
172                 requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
173                 requestHeadersBuilder.append(":  ?1");
174
175
176                 // Set the `Accept` header property.
177                 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");
178
179                 // Add the `Accept` header to the string builder and format the text.
180                 requestHeadersBuilder.append(System.getProperty("line.separator"));
181                 requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
182                 requestHeadersBuilder.append(":  ");
183                 requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
184
185
186                 // Set the `Accept-Language` header property.
187                 httpUrlConnection.setRequestProperty("Accept-Language", localeString);
188
189                 // Add the `Accept-Language` header to the string builder and format the text.
190                 requestHeadersBuilder.append(System.getProperty("line.separator"));
191                 requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
192                 requestHeadersBuilder.append(":  ");
193                 requestHeadersBuilder.append(localeString);
194
195
196                 // Get the cookies for the current domain.
197                 String cookiesString = CookieManager.getInstance().getCookie(url.toString());
198
199                 // Only process the cookies if they are not null.
200                 if (cookiesString != null) {
201                     // Add the cookies to the header property.
202                     httpUrlConnection.setRequestProperty("Cookie", cookiesString);
203
204                     // Add the cookie header to the string builder and format the text.
205                     requestHeadersBuilder.append(System.getProperty("line.separator"));
206                     requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
207                     requestHeadersBuilder.append(":  ");
208                     requestHeadersBuilder.append(cookiesString);
209                 }
210
211
212                 // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
213                 // Add the `Accept-Encoding` header to the string builder and format the text.
214                 requestHeadersBuilder.append(System.getProperty("line.separator"));
215                 requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
216                 requestHeadersBuilder.append(":  gzip");
217
218                 // Ignore SSL errors if requested.
219                 if (ignoreSslErrors){
220                     // Create a new host name verifier.
221                     HostnameVerifier hostnameVerifier = (hostname, sslSession) -> {
222                         // Allow all host names.
223                         return true;
224                     };
225
226                     // Create a new trust manager.  Lint wants to warn us that it is hard to securely implement an X509 trust manager.
227                     // 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.
228                     @SuppressLint("CustomX509TrustManager") TrustManager[] trustManager = new TrustManager[] {
229                             new X509TrustManager() {
230                                 @SuppressLint("TrustAllX509TrustManager")
231                                 @Override
232                                 public void checkClientTrusted(X509Certificate[] chain, String authType) {
233                                     // Do nothing, which trusts all client certificates.
234                                 }
235
236                                 @SuppressLint("TrustAllX509TrustManager")
237                                 @Override
238                                 public void checkServerTrusted(X509Certificate[] chain, String authType) {
239                                     // Do nothing, which trusts all server certificates.
240                                 }
241
242                                 @Override
243                                 public X509Certificate[] getAcceptedIssuers() {
244                                     return null;
245                                 }
246                             }
247                     };
248
249                     // Get an SSL context.  `TLS` provides a base instance available from API 1.  <https://developer.android.com/reference/javax/net/ssl/SSLContext>
250                     SSLContext sslContext = SSLContext.getInstance("TLS");
251
252                     // Initialize the SSL context with the blank trust manager.
253                     sslContext.init(null, trustManager, new SecureRandom());
254
255                     // Get the SSL socket factory with the blank trust manager.
256                     SSLSocketFactory socketFactory = sslContext.getSocketFactory();
257
258                     // Set the HTTPS URL Connection to use the blank host name verifier.
259                     ((HttpsURLConnection) httpUrlConnection).setHostnameVerifier(hostnameVerifier);
260
261                     // Set the HTTPS URL connection to use the socket factory with the blank trust manager.
262                     ((HttpsURLConnection) httpUrlConnection).setSSLSocketFactory(socketFactory);
263                 }
264
265                 // 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.
266                 try {
267                     // Get the response code, which causes the connection to the server to be made.
268                     int responseCode = httpUrlConnection.getResponseCode();
269
270                     // Populate the response message string builder.
271                     responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
272                     responseMessageBuilder.append(":  ");
273                     responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
274
275                     // Initialize the iteration variable.
276                     int i = 0;
277
278                     // Iterate through the received header fields.
279                     while (httpUrlConnection.getHeaderField(i) != null) {
280                         // Add a new line if there is already information in the string builder.
281                         if (i > 0) {
282                             responseHeadersBuilder.append(System.getProperty("line.separator"));
283                         }
284
285                         // Add the header to the string builder and format the text.
286                         responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
287                         responseHeadersBuilder.append(":  ");
288                         responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
289
290                         // Increment the iteration variable.
291                         i++;
292                     }
293
294                     // Instantiate an input stream for the response body.
295                     InputStream inputStream;
296
297                     // Get the correct input stream based on the response code.
298                     if (responseCode == 404) {  // Get the error stream.
299                         inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
300                     } else {  // Get the response body stream.
301                         inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
302                     }
303
304                     // Initialize the byte array output stream and the conversion buffer byte array.
305                     ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
306                     byte[] conversionBufferByteArray = new byte[1024];
307
308                     // Define the buffer length variable.
309                     int bufferLength;
310
311                     try {
312                         // 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.
313                         while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) {  // Proceed while the amount of data stored in the buffer is > 0.
314                             // Write the contents of the conversion buffer to the byte array output stream.
315                             byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
316                         }
317                     } catch (IOException exception) {
318                         // Return the error message.
319                         webViewSource.returnError(exception.toString());
320                     }
321
322                     // Close the input stream.
323                     inputStream.close();
324
325                     // Populate the response body string with the contents of the byte array output stream.
326                     responseBodyBuilder.append(byteArrayOutputStream.toString());
327                 } finally {
328                     // Disconnect HTTP URL connection.
329                     httpUrlConnection.disconnect();
330                 }
331             } catch (Exception exception) {
332                 // Return the error message.
333                 webViewSource.returnError(exception.toString());
334             }
335         }
336
337         // Return the spannable string builders.
338         return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
339     }
340 }