]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/backgroundtasks/GetSourceBackgroundTask.kt
Migrate five classes to Kotlin. https://redmine.stoutner.com/issues/950
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / backgroundtasks / GetSourceBackgroundTask.kt
1 /*
2  * Copyright © 2017-2023 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.graphics.Typeface
25 import android.net.Uri
26 import android.text.SpannableStringBuilder
27 import android.text.Spanned
28 import android.text.style.StyleSpan
29 import android.webkit.CookieManager
30
31 import com.stoutner.privacybrowser.viewmodels.WebViewSource
32
33 import java.io.BufferedInputStream
34 import java.io.BufferedReader
35 import java.io.ByteArrayOutputStream
36 import java.io.IOException
37 import java.io.InputStream
38 import java.io.InputStreamReader
39
40 import java.net.HttpURLConnection
41 import java.net.Proxy
42 import java.net.URL
43
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.SSLSession
51 import javax.net.ssl.TrustManager
52 import javax.net.ssl.X509TrustManager
53
54
55 class GetSourceBackgroundTask {
56     fun acquire(urlString: String, userAgent: String, localeString: String, proxy: Proxy, contentResolver: ContentResolver, webViewSource: WebViewSource, ignoreSslErrors: Boolean):
57             Array<SpannableStringBuilder> {
58
59         // Initialize the spannable string builders.
60         val requestHeadersBuilder = SpannableStringBuilder()
61         val responseMessageBuilder = SpannableStringBuilder()
62         val responseHeadersBuilder = SpannableStringBuilder()
63         val responseBodyBuilder = SpannableStringBuilder()
64
65         if (urlString.startsWith("content://")) {  // This is a content URL.
66             // Attempt to read the content data.  Return an error if this fails.
67             try {
68                 // Get a URI for the content URL.
69                 val contentUri = Uri.parse(urlString)
70
71                 // Get a cursor with metadata about the content URL.
72                 val contentCursor = contentResolver.query(contentUri, null, null, null, null)!!
73
74                 // Move the content cursor to the first row.
75                 contentCursor.moveToFirst()
76
77                 // Populate the response header.
78                 for (i in 0 until contentCursor.columnCount) {
79                     // Add a new line if this is not the first entry.
80                     if (i > 0)
81                         responseHeadersBuilder.append(System.getProperty("line.separator"))
82
83                     // Add each header to the string builder.
84                     responseHeadersBuilder.append(contentCursor.getColumnName(i), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
85                     responseHeadersBuilder.append(":  ")
86                     responseHeadersBuilder.append(contentCursor.getString(i))
87                 }
88
89                 // Close the content cursor.
90                 contentCursor.close()
91
92                 // Create a buffered string reader for the content data.
93                 val bufferedReader = BufferedReader(InputStreamReader(contentResolver.openInputStream(contentUri)))
94
95                 // Create a buffered string reader for the content data.
96                 var contentLineString: String?
97
98                 // Get the data from the buffered reader one line at a time.
99                 while (bufferedReader.readLine().also { contentLineString = it } != null) {
100                     // Add the line to the response body builder.
101                     responseBodyBuilder.append(contentLineString)
102
103                     // Append a new line.
104                     responseBodyBuilder.append("\n")
105                 }
106             } catch (exception: Exception) {
107                 // Return the error message.
108                 webViewSource.returnError(exception.toString())
109             }
110         } else {  // This is not a content URL.
111             // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
112             try {
113                 // Get the current URL from the main activity.
114                 val url = URL(urlString)
115
116                 // Open a connection to the URL.  No data is actually sent at this point.
117                 val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
118
119                 // Set the `Host` header property.
120                 httpUrlConnection.setRequestProperty("Host", url.host)
121
122                 // Add the `Host` header to the string builder and format the text.
123                 requestHeadersBuilder.append("Host", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
124                 requestHeadersBuilder.append(":  ")
125                 requestHeadersBuilder.append(url.host)
126
127
128                 // Set the `Connection` header property.
129                 httpUrlConnection.setRequestProperty("Connection", "keep-alive")
130
131                 // Add the `Connection` header to the string builder and format the text.
132                 requestHeadersBuilder.append(System.getProperty("line.separator"))
133                 requestHeadersBuilder.append("Connection", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
134                 requestHeadersBuilder.append(":  keep-alive")
135
136
137                 // Set the `Upgrade-Insecure-Requests` header property.
138                 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1")
139
140                 // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
141                 requestHeadersBuilder.append(System.getProperty("line.separator"))
142                 requestHeadersBuilder.append("Upgrade-Insecure-Requests", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
143                 requestHeadersBuilder.append(":  1")
144
145
146                 // Set the `User-Agent` header property.
147                 httpUrlConnection.setRequestProperty("User-Agent", userAgent)
148
149                 // Add the `User-Agent` header to the string builder and format the text.
150                 requestHeadersBuilder.append(System.getProperty("line.separator"))
151                 requestHeadersBuilder.append("User-Agent", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
152                 requestHeadersBuilder.append(":  ")
153                 requestHeadersBuilder.append(userAgent)
154
155
156                 // Set the `Sec-Fetch-Site` header property.
157                 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none")
158
159                 // Add the `Sec-Fetch-Site` header to the string builder and format the text.
160                 requestHeadersBuilder.append(System.getProperty("line.separator"))
161                 requestHeadersBuilder.append("Sec-Fetch-Site", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
162                 requestHeadersBuilder.append(":  none")
163
164
165                 // Set the `Sec-Fetch-Mode` header property.
166                 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate")
167
168                 // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
169                 requestHeadersBuilder.append(System.getProperty("line.separator"))
170                 requestHeadersBuilder.append("Sec-Fetch-Mode", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
171                 requestHeadersBuilder.append(":  navigate")
172
173
174                 // Set the `Sec-Fetch-User` header property.
175                 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1")
176
177                 // Add the `Sec-Fetch-User` header to the string builder and format the text.
178                 requestHeadersBuilder.append(System.getProperty("line.separator"))
179                 requestHeadersBuilder.append("Sec-Fetch-User", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
180                 requestHeadersBuilder.append(":  ?1")
181
182
183                 // Set the `Accept` header property.
184                 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")
185
186                 // Add the `Accept` header to the string builder and format the text.
187                 requestHeadersBuilder.append(System.getProperty("line.separator"))
188                 requestHeadersBuilder.append("Accept", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
189                 requestHeadersBuilder.append(":  ")
190                 requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
191
192
193                 // Set the `Accept-Language` header property.
194                 httpUrlConnection.setRequestProperty("Accept-Language", localeString)
195
196                 // Add the `Accept-Language` header to the string builder and format the text.
197                 requestHeadersBuilder.append(System.getProperty("line.separator"))
198                 requestHeadersBuilder.append("Accept-Language", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
199                 requestHeadersBuilder.append(":  ")
200                 requestHeadersBuilder.append(localeString)
201
202
203                 // Get the cookies for the current domain.
204                 val cookiesString = CookieManager.getInstance().getCookie(url.toString())
205
206                 // Only process the cookies if they are not null.
207                 if (cookiesString != null) {
208                     // Add the cookies to the header property.
209                     httpUrlConnection.setRequestProperty("Cookie", cookiesString)
210
211                     // Add the cookie header to the string builder and format the text.
212                     requestHeadersBuilder.append(System.getProperty("line.separator"))
213                     requestHeadersBuilder.append("Cookie", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
214                     requestHeadersBuilder.append(":  ")
215                     requestHeadersBuilder.append(cookiesString)
216                 }
217
218
219                 // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
220                 // Add the `Accept-Encoding` header to the string builder and format the text.
221                 requestHeadersBuilder.append(System.getProperty("line.separator"))
222                 requestHeadersBuilder.append("Accept-Encoding", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
223                 requestHeadersBuilder.append(":  gzip")
224
225                 // Ignore SSL errors if requested.
226                 if (ignoreSslErrors) {
227                     // Create a new host name verifier that allows all host names without checking for SSL errors.
228                     val hostnameVerifier = HostnameVerifier { _: String?, _: SSLSession? -> true }
229
230                     // Create a new trust manager.  Lint wants to warn us that it is hard to securely implement an X509 trust manager.
231                     // 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.
232                     @SuppressLint("CustomX509TrustManager") val trustManager = arrayOf<TrustManager>(
233                         object : X509TrustManager {
234                             @SuppressLint("TrustAllX509TrustManager")
235                             override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
236                                 // Do nothing, which trusts all client certificates.
237                             }
238
239                             @SuppressLint("TrustAllX509TrustManager")
240                             override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
241                                 // Do nothing, which trusts all server certificates.
242                             }
243
244                             override fun getAcceptedIssuers(): Array<X509Certificate>? {
245                                 return null
246                             }
247                         }
248                     )
249
250                     // Get an SSL context.  `TLS` provides a base instance available from API 1.  <https://developer.android.com/reference/javax/net/ssl/SSLContext>
251                     val sslContext = SSLContext.getInstance("TLS")
252
253                     // Initialize the SSL context with the blank trust manager.
254                     sslContext.init(null, trustManager, SecureRandom())
255
256                     // Get the SSL socket factory with the blank trust manager.
257                     val socketFactory = sslContext.socketFactory
258
259                     // Set the HTTPS URL Connection to use the blank host name verifier.
260                     (httpUrlConnection as HttpsURLConnection).hostnameVerifier = hostnameVerifier
261
262                     // Set the HTTPS URL connection to use the socket factory with the blank trust manager.
263                     httpUrlConnection.sslSocketFactory = socketFactory
264                 }
265
266                 // 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.
267                 try {
268                     // Get the response code, which causes the connection to the server to be made.
269                     val responseCode = httpUrlConnection.responseCode
270
271                     // Populate the response message string builder.
272                     responseMessageBuilder.append(responseCode.toString(), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
273                     responseMessageBuilder.append(":  ")
274                     responseMessageBuilder.append(httpUrlConnection.responseMessage)
275
276                     // Initialize the iteration variable.
277                     var i = 0
278
279                     // Iterate through the received header fields.
280                     while (httpUrlConnection.getHeaderField(i) != null) {
281                         // Add a new line if there is already information in the string builder.
282                         if (i > 0)
283                             responseHeadersBuilder.append(System.getProperty("line.separator"))
284
285                         // Add the header to the string builder and format the text.
286                         responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), 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                     // Get the correct input stream based on the response code.
295                     val inputStream: InputStream = if (responseCode == 404)  // Get the error stream.
296                         BufferedInputStream(httpUrlConnection.errorStream)
297                     else  // Get the response body stream.
298                         BufferedInputStream(httpUrlConnection.inputStream)
299
300                     // Initialize the byte array output stream and the conversion buffer byte array.
301                     val byteArrayOutputStream = ByteArrayOutputStream()
302                     val conversionBufferByteArray = ByteArray(1024)
303
304                     // Define the buffer length variable.
305                     var bufferLength: Int
306
307                     try {
308                         // 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.
309                         while (inputStream.read(conversionBufferByteArray).also { bufferLength = it } > 0) {  // Proceed while the amount of data stored in the buffer is > 0.
310                             // Write the contents of the conversion buffer to the byte array output stream.
311                             byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength)
312                         }
313                     } catch (exception: IOException) {
314                         // Return the error message.
315                         webViewSource.returnError(exception.toString())
316                     }
317
318                     // Close the input stream.
319                     inputStream.close()
320
321                     // Populate the response body string with the contents of the byte array output stream.
322                     responseBodyBuilder.append(byteArrayOutputStream.toString())
323                 } finally {
324                     // Disconnect HTTP URL connection.
325                     httpUrlConnection.disconnect()
326                 }
327             } catch (exception: Exception) {
328                 // Return the error message.
329                 webViewSource.returnError(exception.toString())
330             }
331         }
332
333         // Return the spannable string builders.
334         return arrayOf(requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder)
335     }
336 }