]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/backgroundtasks/GetHeadersBackgroundTask.kt
First wrong button text in View Headers in night theme. https://redmine.stoutner...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / backgroundtasks / GetHeadersBackgroundTask.kt
1 /*
2  * Copyright 2017-2024 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.app.Application
24 import android.content.ContentResolver
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.R
33 import com.stoutner.privacybrowser.viewmodels.HeadersViewModel
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
42 import java.net.HttpURLConnection
43 import java.net.Proxy
44 import java.net.URL
45
46 import java.security.SecureRandom
47 import java.security.cert.X509Certificate
48
49 import javax.net.ssl.HostnameVerifier
50 import javax.net.ssl.HttpsURLConnection
51 import javax.net.ssl.SSLContext
52 import javax.net.ssl.SSLSession
53 import javax.net.ssl.TrustManager
54 import javax.net.ssl.X509TrustManager
55
56
57 class GetHeadersBackgroundTask {
58     fun acquire(application: Application, urlString: String, userAgent: String, localeString: String, proxy: Proxy, contentResolver: ContentResolver, headersViewModel: HeadersViewModel, ignoreSslErrors: Boolean):
59             Array<SpannableStringBuilder> {
60
61         // Initialize the spannable string builders.
62         val sslInformationBuilder = SpannableStringBuilder()
63         val appliedCipherBuilder = SpannableStringBuilder()
64         val availableCiphersBuilder = SpannableStringBuilder()
65         val sslCertificateBuilder = SpannableStringBuilder()
66         val requestHeadersBuilder = SpannableStringBuilder()
67         val responseMessageBuilder = SpannableStringBuilder()
68         val responseHeadersBuilder = SpannableStringBuilder()
69         val responseBodyBuilder = SpannableStringBuilder()
70
71         // Get the colon string.
72         val colonString = application.getString(R.string.colon)
73         val newLineString = System.getProperty("line.separator")
74
75         if (urlString.startsWith("content://")) {  // This is a content URL.
76             // Attempt to read the content data.  Return an error if this fails.
77             try {
78                 // Get a URI for the content URL.
79                 val contentUri = Uri.parse(urlString)
80
81                 // Get a cursor with metadata about the content URL.
82                 val contentCursor = contentResolver.query(contentUri, null, null, null, null)!!
83
84                 // Move the content cursor to the first row.
85                 contentCursor.moveToFirst()
86
87                 // Populate the response header.
88                 for (i in 0 until contentCursor.columnCount) {
89                     // Add a new line if this is not the first entry.
90                     if (i > 0)
91                         responseHeadersBuilder.append(newLineString)
92
93                     // Add each header to the string builder.
94                     responseHeadersBuilder.append(contentCursor.getColumnName(i), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
95                     responseHeadersBuilder.append(colonString)
96                     responseHeadersBuilder.append(contentCursor.getString(i))
97                 }
98
99                 // Close the content cursor.
100                 contentCursor.close()
101
102                 // Create a buffered string reader for the content data.
103                 val bufferedReader = BufferedReader(InputStreamReader(contentResolver.openInputStream(contentUri)))
104
105                 // Create a content line string.
106                 var contentLineString: String?
107
108                 // Get the data from the buffered reader one line at a time.
109                 while (bufferedReader.readLine().also { contentLineString = it } != null) {
110                     // Add the line to the response body builder.
111                     responseBodyBuilder.append(contentLineString)
112
113                     // Append a new line.
114                     responseBodyBuilder.append("\n")
115                 }
116             } catch (exception: Exception) {
117                 // Return the error message.
118                 headersViewModel.returnError(exception.toString())
119             }
120         } else {  // This is not a content URL.
121             // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
122             try {
123                 // Get the current URL from the main activity.
124                 val url = URL(urlString)
125
126                 // Open a connection to the URL.  No data is actually sent at this point.
127                 val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
128
129                 // Set the `Host` header property.
130                 httpUrlConnection.setRequestProperty("Host", url.host)
131
132                 // Add the `Host` header to the string builder and format the text.
133                 requestHeadersBuilder.append("Host", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
134                 requestHeadersBuilder.append(colonString)
135                 requestHeadersBuilder.append(url.host)
136
137
138                 // Set the `Connection` header property.
139                 httpUrlConnection.setRequestProperty("Connection", "keep-alive")
140
141                 // Add the `Connection` header to the string builder and format the text.
142                 requestHeadersBuilder.append(newLineString)
143                 requestHeadersBuilder.append("Connection", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
144                 requestHeadersBuilder.append(colonString)
145                 requestHeadersBuilder.append("keep-alive")
146
147
148                 // Set the `Upgrade-Insecure-Requests` header property.
149                 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1")
150
151                 // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
152                 requestHeadersBuilder.append(newLineString)
153                 requestHeadersBuilder.append("Upgrade-Insecure-Requests", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
154                 requestHeadersBuilder.append(colonString)
155                 requestHeadersBuilder.append("1")
156
157
158                 // Set the `User-Agent` header property.
159                 httpUrlConnection.setRequestProperty("User-Agent", userAgent)
160
161                 // Add the `User-Agent` header to the string builder and format the text.
162                 requestHeadersBuilder.append(newLineString)
163                 requestHeadersBuilder.append("User-Agent", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
164                 requestHeadersBuilder.append(colonString)
165                 requestHeadersBuilder.append(userAgent)
166
167
168                 // Set the `Sec-Fetch-Site` header property.
169                 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none")
170
171                 // Add the `Sec-Fetch-Site` header to the string builder and format the text.
172                 requestHeadersBuilder.append(newLineString)
173                 requestHeadersBuilder.append("Sec-Fetch-Site", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
174                 requestHeadersBuilder.append(colonString)
175                 requestHeadersBuilder.append("none")
176
177
178                 // Set the `Sec-Fetch-Mode` header property.
179                 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate")
180
181                 // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
182                 requestHeadersBuilder.append(newLineString)
183                 requestHeadersBuilder.append("Sec-Fetch-Mode", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
184                 requestHeadersBuilder.append(colonString)
185                 requestHeadersBuilder.append("navigate")
186
187
188                 // Set the `Sec-Fetch-User` header property.
189                 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1")
190
191                 // Add the `Sec-Fetch-User` header to the string builder and format the text.
192                 requestHeadersBuilder.append(newLineString)
193                 requestHeadersBuilder.append("Sec-Fetch-User", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
194                 requestHeadersBuilder.append(colonString)
195                 requestHeadersBuilder.append("?1")
196
197
198                 // Set the `Accept` header property.
199                 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")
200
201                 // Add the `Accept` header to the string builder and format the text.
202                 requestHeadersBuilder.append(newLineString)
203                 requestHeadersBuilder.append("Accept", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
204                 requestHeadersBuilder.append(colonString)
205                 requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
206
207
208                 // Set the `Accept-Language` header property.
209                 httpUrlConnection.setRequestProperty("Accept-Language", localeString)
210
211                 // Add the `Accept-Language` header to the string builder and format the text.
212                 requestHeadersBuilder.append(newLineString)
213                 requestHeadersBuilder.append("Accept-Language", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
214                 requestHeadersBuilder.append(colonString)
215                 requestHeadersBuilder.append(localeString)
216
217
218                 // Get the cookies for the current domain.
219                 val cookiesString = CookieManager.getInstance().getCookie(url.toString())
220
221                 // Only process the cookies if they are not null.
222                 if (cookiesString != null) {
223                     // Add the cookies to the header property.
224                     httpUrlConnection.setRequestProperty("Cookie", cookiesString)
225
226                     // Add the cookie header to the string builder and format the text.
227                     requestHeadersBuilder.append(newLineString)
228                     requestHeadersBuilder.append("Cookie", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
229                     requestHeadersBuilder.append(colonString)
230                     requestHeadersBuilder.append(cookiesString)
231                 }
232
233
234                 // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default.  If the property is manually set, than `HttpUrlConnection` does not process the decoding.
235                 // Add the `Accept-Encoding` header to the string builder and format the text.
236                 requestHeadersBuilder.append(newLineString)
237                 requestHeadersBuilder.append("Accept-Encoding", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
238                 requestHeadersBuilder.append(colonString)
239                 requestHeadersBuilder.append("gzip")
240
241                 // Ignore SSL errors if requested.
242                 if (ignoreSslErrors) {
243                     // Create a new host name verifier that allows all host names without checking for SSL errors.
244                     val hostnameVerifier = HostnameVerifier { _: String?, _: SSLSession? -> true }
245
246                     // Create a new trust manager.  Lint wants to warn us that it is hard to securely implement an X509 trust manager.
247                     // 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.
248                     @SuppressLint("CustomX509TrustManager") val trustManager = arrayOf<TrustManager>(
249                         object : X509TrustManager {
250                             @SuppressLint("TrustAllX509TrustManager")
251                             override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
252                                 // Do nothing, which trusts all client certificates.
253                             }
254
255                             @SuppressLint("TrustAllX509TrustManager")
256                             override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
257                                 // Do nothing, which trusts all server certificates.
258                             }
259
260                             override fun getAcceptedIssuers(): Array<X509Certificate>? {
261                                 return null
262                             }
263                         }
264                     )
265
266                     // Get an SSL context.  `TLS` provides a base instance available from API 1.  <https://developer.android.com/reference/javax/net/ssl/SSLContext>
267                     val sslContext = SSLContext.getInstance("TLS")
268
269                     // Initialize the SSL context with the blank trust manager.
270                     sslContext.init(null, trustManager, SecureRandom())
271
272                     // Get the SSL socket factory with the blank trust manager.
273                     val socketFactory = sslContext.socketFactory
274
275                     // Set the HTTPS URL Connection to use the blank host name verifier.
276                     (httpUrlConnection as HttpsURLConnection).hostnameVerifier = hostnameVerifier
277
278                     // Set the HTTPS URL connection to use the socket factory with the blank trust manager.
279                     httpUrlConnection.sslSocketFactory = socketFactory
280                 }
281
282                 // 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.
283                 try {
284                     // Get the response code, which causes the connection to the server to be made.
285                     val responseCode = httpUrlConnection.responseCode
286
287                     // Try to populate the SSL certificate information.
288                     try {
289                         // Get the applied cipher suite string.
290                         val appliedCipherString = (httpUrlConnection as HttpsURLConnection).cipherSuite
291
292                         // Populate the applied cipher builder, returned separately.
293                         appliedCipherBuilder.append(appliedCipherString)
294
295                         // Append the applied cipher suite to the SSL information builder.
296                         sslInformationBuilder.append(application.getString(R.string.applied_cipher), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
297                         sslInformationBuilder.append(colonString)
298                         sslInformationBuilder.append(appliedCipherString)
299                         sslInformationBuilder.append(newLineString)
300
301                         // Append the peer principal to the SSL information builder.
302                         sslInformationBuilder.append(application.getString(R.string.peer_principal), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
303                         sslInformationBuilder.append(colonString)
304                         sslInformationBuilder.append(httpUrlConnection.peerPrincipal.toString())
305                         sslInformationBuilder.append(newLineString)
306
307                         // Get the server certificate.
308                         val serverCertificate = httpUrlConnection.serverCertificates[0]
309
310                         // Append the certificate type to the SSL information builder.
311                         sslInformationBuilder.append(application.getString(R.string.certificate_type), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
312                         sslInformationBuilder.append(colonString)
313                         sslInformationBuilder.append(serverCertificate.type)
314                         sslInformationBuilder.append(newLineString)
315
316                         // Append the certificate hash code to the SSL information builder.
317                         sslInformationBuilder.append(application.getString(R.string.certificate_hash_code), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
318                         sslInformationBuilder.append(colonString)
319                         sslInformationBuilder.append(serverCertificate.hashCode().toString())
320
321                         // Get the available cipher suites string array.
322                         val availableCipherSuitesStringArray = httpUrlConnection.sslSocketFactory.defaultCipherSuites
323
324                         // Get the available cipher suites string array size.
325                         val availableCipherSuitesStringArraySize = availableCipherSuitesStringArray.size
326
327                         // Populate the available cipher suites, returned separately.
328                         for (i in 0 until availableCipherSuitesStringArraySize) {
329                             // Append a new line if a cipher is already populated.
330                             if (i > 0)
331                                 availableCiphersBuilder.append(newLineString)
332
333                             // Get the current cipher suite.
334                             val currentCipherSuite = availableCipherSuitesStringArray[i]
335
336                             // Append the current cipher to the list.
337                             availableCiphersBuilder.append(currentCipherSuite)
338                         }
339
340                         // Populate the SSL certificate, returned separately.
341                         sslCertificateBuilder.append(serverCertificate.toString())
342                     } catch (exception: Exception) {
343                         // Do nothing.
344                     }
345
346                     // Populate the response message string builder.
347                     responseMessageBuilder.append(responseCode.toString(), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
348                     responseMessageBuilder.append(colonString)
349                     responseMessageBuilder.append(httpUrlConnection.responseMessage)
350
351                     // Initialize the iteration variable.
352                     var i = 0
353
354                     // Iterate through the received header fields.
355                     while (httpUrlConnection.getHeaderField(i) != null) {
356                         // Add a new line if there is already information in the string builder.
357                         if (i > 0)
358                             responseHeadersBuilder.append(newLineString)
359
360                         // Add the header to the string builder and format the text.
361                         responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
362                         responseHeadersBuilder.append(colonString)
363                         responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i))
364
365                         // Increment the iteration variable.
366                         i++
367                     }
368
369                     // Get the correct input stream based on the response code.
370                     val inputStream: InputStream = if (responseCode == 404)  // Get the error stream.
371                         BufferedInputStream(httpUrlConnection.errorStream)
372                     else  // Get the response body stream.
373                         BufferedInputStream(httpUrlConnection.inputStream)
374
375                     // Initialize the byte array output stream and the conversion buffer byte array.
376                     val byteArrayOutputStream = ByteArrayOutputStream()
377                     val conversionBufferByteArray = ByteArray(1024)
378
379                     // Define the buffer length variable.
380                     var bufferLength: Int
381
382                     try {
383                         // 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.
384                         while (inputStream.read(conversionBufferByteArray).also { bufferLength = it } > 0) {  // Proceed while the amount of data stored in the buffer is > 0.
385                             // Write the contents of the conversion buffer to the byte array output stream.
386                             byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength)
387                         }
388                     } catch (exception: IOException) {
389                         // Return the error message.
390                         headersViewModel.returnError(exception.toString())
391                     }
392
393                     // Close the input stream.
394                     inputStream.close()
395
396                     // Populate the response body string with the contents of the byte array output stream.
397                     responseBodyBuilder.append(byteArrayOutputStream.toString())
398                 } finally {
399                     // Disconnect HTTP URL connection.
400                     httpUrlConnection.disconnect()
401                 }
402             } catch (exception: Exception) {
403                 // Return the error message.
404                 headersViewModel.returnError(exception.toString())
405             }
406         }
407
408         // Return the spannable string builders.
409         return arrayOf(sslInformationBuilder, appliedCipherBuilder, availableCiphersBuilder, sslCertificateBuilder, requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder)
410     }
411 }