2 * Copyright 2017-2024 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.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
32 import com.stoutner.privacybrowser.R
33 import com.stoutner.privacybrowser.viewmodels.HeadersViewModel
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
42 import java.net.HttpURLConnection
46 import java.security.SecureRandom
47 import java.security.cert.X509Certificate
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
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> {
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()
71 // Get the colon string.
72 val colonString = application.getString(R.string.colon)
73 val newLineString = System.getProperty("line.separator")
75 if (urlString.startsWith("content://")) { // This is a content URL.
76 // Attempt to read the content data. Return an error if this fails.
78 // Get a URI for the content URL.
79 val contentUri = Uri.parse(urlString)
81 // Get a cursor with metadata about the content URL.
82 val contentCursor = contentResolver.query(contentUri, null, null, null, null)!!
84 // Move the content cursor to the first row.
85 contentCursor.moveToFirst()
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.
91 responseHeadersBuilder.append(newLineString)
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))
99 // Close the content cursor.
100 contentCursor.close()
102 // Create a buffered string reader for the content data.
103 val bufferedReader = BufferedReader(InputStreamReader(contentResolver.openInputStream(contentUri)))
105 // Create a content line string.
106 var contentLineString: String?
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)
113 // Append a new line.
114 responseBodyBuilder.append("\n")
116 } catch (exception: Exception) {
117 // Return the error message.
118 headersViewModel.returnError(exception.toString())
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`.
123 // Get the current URL from the main activity.
124 val url = URL(urlString)
126 // Open a connection to the URL. No data is actually sent at this point.
127 val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
129 // Set the `Host` header property.
130 httpUrlConnection.setRequestProperty("Host", url.host)
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)
138 // Set the `Connection` header property.
139 httpUrlConnection.setRequestProperty("Connection", "keep-alive")
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")
148 // Set the `Upgrade-Insecure-Requests` header property.
149 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1")
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")
158 // Set the `User-Agent` header property.
159 httpUrlConnection.setRequestProperty("User-Agent", userAgent)
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)
168 // Set the `Sec-Fetch-Site` header property.
169 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none")
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")
178 // Set the `Sec-Fetch-Mode` header property.
179 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate")
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")
188 // Set the `Sec-Fetch-User` header property.
189 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1")
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")
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")
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")
208 // Set the `Accept-Language` header property.
209 httpUrlConnection.setRequestProperty("Accept-Language", localeString)
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)
218 // Get the cookies for the current domain.
219 val cookiesString = CookieManager.getInstance().getCookie(url.toString())
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)
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)
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")
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 }
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.
255 @SuppressLint("TrustAllX509TrustManager")
256 override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
257 // Do nothing, which trusts all server certificates.
260 override fun getAcceptedIssuers(): Array<X509Certificate>? {
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")
269 // Initialize the SSL context with the blank trust manager.
270 sslContext.init(null, trustManager, SecureRandom())
272 // Get the SSL socket factory with the blank trust manager.
273 val socketFactory = sslContext.socketFactory
275 // Set the HTTPS URL Connection to use the blank host name verifier.
276 (httpUrlConnection as HttpsURLConnection).hostnameVerifier = hostnameVerifier
278 // Set the HTTPS URL connection to use the socket factory with the blank trust manager.
279 httpUrlConnection.sslSocketFactory = socketFactory
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.
284 // Get the response code, which causes the connection to the server to be made.
285 val responseCode = httpUrlConnection.responseCode
287 // Try to populate the SSL certificate information.
289 // Get the applied cipher suite string.
290 val appliedCipherString = (httpUrlConnection as HttpsURLConnection).cipherSuite
292 // Populate the applied cipher builder, returned separately.
293 appliedCipherBuilder.append(appliedCipherString)
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)
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)
307 // Get the server certificate.
308 val serverCertificate = httpUrlConnection.serverCertificates[0]
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)
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())
321 // Get the available cipher suites string array.
322 val availableCipherSuitesStringArray = httpUrlConnection.sslSocketFactory.defaultCipherSuites
324 // Get the available cipher suites string array size.
325 val availableCipherSuitesStringArraySize = availableCipherSuitesStringArray.size
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.
331 availableCiphersBuilder.append(newLineString)
333 // Get the current cipher suite.
334 val currentCipherSuite = availableCipherSuitesStringArray[i]
336 // Append the current cipher to the list.
337 availableCiphersBuilder.append(currentCipherSuite)
340 // Populate the SSL certificate, returned separately.
341 sslCertificateBuilder.append(serverCertificate.toString())
342 } catch (exception: Exception) {
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)
351 // Initialize the iteration variable.
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.
358 responseHeadersBuilder.append(newLineString)
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))
365 // Increment the iteration variable.
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)
375 // Initialize the byte array output stream and the conversion buffer byte array.
376 val byteArrayOutputStream = ByteArrayOutputStream()
377 val conversionBufferByteArray = ByteArray(1024)
379 // Define the buffer length variable.
380 var bufferLength: Int
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)
388 } catch (exception: IOException) {
389 // Return the error message.
390 headersViewModel.returnError(exception.toString())
393 // Close the input stream.
396 // Populate the response body string with the contents of the byte array output stream.
397 responseBodyBuilder.append(byteArrayOutputStream.toString())
399 // Disconnect HTTP URL connection.
400 httpUrlConnection.disconnect()
402 } catch (exception: Exception) {
403 // Return the error message.
404 headersViewModel.returnError(exception.toString())
408 // Return the spannable string builders.
409 return arrayOf(sslInformationBuilder, appliedCipherBuilder, availableCiphersBuilder, sslCertificateBuilder, requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder)