+++ /dev/null
-/*
- * Copyright © 2017-2023 Soren Stoutner <soren@stoutner.com>.
- *
- * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
- *
- * Privacy Browser Android is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Privacy Browser Android is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Privacy Browser Android. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.stoutner.privacybrowser.backgroundtasks
-
-import android.annotation.SuppressLint
-import android.content.ContentResolver
-import android.graphics.Typeface
-import android.net.Uri
-import android.text.SpannableStringBuilder
-import android.text.Spanned
-import android.text.style.StyleSpan
-import android.webkit.CookieManager
-
-import com.stoutner.privacybrowser.viewmodels.WebViewSource
-
-import java.io.BufferedInputStream
-import java.io.BufferedReader
-import java.io.ByteArrayOutputStream
-import java.io.IOException
-import java.io.InputStream
-import java.io.InputStreamReader
-
-import java.net.HttpURLConnection
-import java.net.Proxy
-import java.net.URL
-
-import java.security.SecureRandom
-import java.security.cert.X509Certificate
-
-import javax.net.ssl.HostnameVerifier
-import javax.net.ssl.HttpsURLConnection
-import javax.net.ssl.SSLContext
-import javax.net.ssl.SSLSession
-import javax.net.ssl.TrustManager
-import javax.net.ssl.X509TrustManager
-
-
-class GetSourceBackgroundTask {
- fun acquire(urlString: String, userAgent: String, localeString: String, proxy: Proxy, contentResolver: ContentResolver, webViewSource: WebViewSource, ignoreSslErrors: Boolean):
- Array<SpannableStringBuilder> {
-
- // Initialize the spannable string builders.
- val requestHeadersBuilder = SpannableStringBuilder()
- val responseMessageBuilder = SpannableStringBuilder()
- val responseHeadersBuilder = SpannableStringBuilder()
- val responseBodyBuilder = SpannableStringBuilder()
-
- if (urlString.startsWith("content://")) { // This is a content URL.
- // Attempt to read the content data. Return an error if this fails.
- try {
- // Get a URI for the content URL.
- val contentUri = Uri.parse(urlString)
-
- // Get a cursor with metadata about the content URL.
- val contentCursor = contentResolver.query(contentUri, null, null, null, null)!!
-
- // Move the content cursor to the first row.
- contentCursor.moveToFirst()
-
- // Populate the response header.
- for (i in 0 until contentCursor.columnCount) {
- // Add a new line if this is not the first entry.
- if (i > 0)
- responseHeadersBuilder.append(System.getProperty("line.separator"))
-
- // Add each header to the string builder.
- responseHeadersBuilder.append(contentCursor.getColumnName(i), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- responseHeadersBuilder.append(": ")
- responseHeadersBuilder.append(contentCursor.getString(i))
- }
-
- // Close the content cursor.
- contentCursor.close()
-
- // Create a buffered string reader for the content data.
- val bufferedReader = BufferedReader(InputStreamReader(contentResolver.openInputStream(contentUri)))
-
- // Create a buffered string reader for the content data.
- var contentLineString: String?
-
- // Get the data from the buffered reader one line at a time.
- while (bufferedReader.readLine().also { contentLineString = it } != null) {
- // Add the line to the response body builder.
- responseBodyBuilder.append(contentLineString)
-
- // Append a new line.
- responseBodyBuilder.append("\n")
- }
- } catch (exception: Exception) {
- // Return the error message.
- webViewSource.returnError(exception.toString())
- }
- } else { // This is not a content URL.
- // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
- try {
- // Get the current URL from the main activity.
- val url = URL(urlString)
-
- // Open a connection to the URL. No data is actually sent at this point.
- val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
-
- // Set the `Host` header property.
- httpUrlConnection.setRequestProperty("Host", url.host)
-
- // Add the `Host` header to the string builder and format the text.
- requestHeadersBuilder.append("Host", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ")
- requestHeadersBuilder.append(url.host)
-
-
- // Set the `Connection` header property.
- httpUrlConnection.setRequestProperty("Connection", "keep-alive")
-
- // Add the `Connection` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
- requestHeadersBuilder.append("Connection", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": keep-alive")
-
-
- // Set the `Upgrade-Insecure-Requests` header property.
- httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1")
-
- // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
- requestHeadersBuilder.append("Upgrade-Insecure-Requests", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": 1")
-
-
- // Set the `User-Agent` header property.
- httpUrlConnection.setRequestProperty("User-Agent", userAgent)
-
- // Add the `User-Agent` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
- requestHeadersBuilder.append("User-Agent", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ")
- requestHeadersBuilder.append(userAgent)
-
-
- // Set the `Sec-Fetch-Site` header property.
- httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none")
-
- // Add the `Sec-Fetch-Site` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
- requestHeadersBuilder.append("Sec-Fetch-Site", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": none")
-
-
- // Set the `Sec-Fetch-Mode` header property.
- httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate")
-
- // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
- requestHeadersBuilder.append("Sec-Fetch-Mode", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": navigate")
-
-
- // Set the `Sec-Fetch-User` header property.
- httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1")
-
- // Add the `Sec-Fetch-User` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
- requestHeadersBuilder.append("Sec-Fetch-User", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ?1")
-
-
- // Set the `Accept` header property.
- 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")
-
- // Add the `Accept` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
- requestHeadersBuilder.append("Accept", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ")
- requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
-
-
- // Set the `Accept-Language` header property.
- httpUrlConnection.setRequestProperty("Accept-Language", localeString)
-
- // Add the `Accept-Language` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
- requestHeadersBuilder.append("Accept-Language", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ")
- requestHeadersBuilder.append(localeString)
-
-
- // Get the cookies for the current domain.
- val cookiesString = CookieManager.getInstance().getCookie(url.toString())
-
- // Only process the cookies if they are not null.
- if (cookiesString != null) {
- // Add the cookies to the header property.
- httpUrlConnection.setRequestProperty("Cookie", cookiesString)
-
- // Add the cookie header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
- requestHeadersBuilder.append("Cookie", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": ")
- requestHeadersBuilder.append(cookiesString)
- }
-
-
- // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default. If the property is manually set, than `HttpUrlConnection` does not process the decoding.
- // Add the `Accept-Encoding` header to the string builder and format the text.
- requestHeadersBuilder.append(System.getProperty("line.separator"))
- requestHeadersBuilder.append("Accept-Encoding", StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- requestHeadersBuilder.append(": gzip")
-
- // Ignore SSL errors if requested.
- if (ignoreSslErrors) {
- // Create a new host name verifier that allows all host names without checking for SSL errors.
- val hostnameVerifier = HostnameVerifier { _: String?, _: SSLSession? -> true }
-
- // Create a new trust manager. Lint wants to warn us that it is hard to securely implement an X509 trust manager.
- // 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.
- @SuppressLint("CustomX509TrustManager") val trustManager = arrayOf<TrustManager>(
- object : X509TrustManager {
- @SuppressLint("TrustAllX509TrustManager")
- override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
- // Do nothing, which trusts all client certificates.
- }
-
- @SuppressLint("TrustAllX509TrustManager")
- override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
- // Do nothing, which trusts all server certificates.
- }
-
- override fun getAcceptedIssuers(): Array<X509Certificate>? {
- return null
- }
- }
- )
-
- // Get an SSL context. `TLS` provides a base instance available from API 1. <https://developer.android.com/reference/javax/net/ssl/SSLContext>
- val sslContext = SSLContext.getInstance("TLS")
-
- // Initialize the SSL context with the blank trust manager.
- sslContext.init(null, trustManager, SecureRandom())
-
- // Get the SSL socket factory with the blank trust manager.
- val socketFactory = sslContext.socketFactory
-
- // Set the HTTPS URL Connection to use the blank host name verifier.
- (httpUrlConnection as HttpsURLConnection).hostnameVerifier = hostnameVerifier
-
- // Set the HTTPS URL connection to use the socket factory with the blank trust manager.
- httpUrlConnection.sslSocketFactory = socketFactory
- }
-
- // 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.
- try {
- // Get the response code, which causes the connection to the server to be made.
- val responseCode = httpUrlConnection.responseCode
-
- // Populate the response message string builder.
- responseMessageBuilder.append(responseCode.toString(), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- responseMessageBuilder.append(": ")
- responseMessageBuilder.append(httpUrlConnection.responseMessage)
-
- // Initialize the iteration variable.
- var i = 0
-
- // Iterate through the received header fields.
- while (httpUrlConnection.getHeaderField(i) != null) {
- // Add a new line if there is already information in the string builder.
- if (i > 0)
- responseHeadersBuilder.append(System.getProperty("line.separator"))
-
- // Add the header to the string builder and format the text.
- responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
- responseHeadersBuilder.append(": ")
- responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i))
-
- // Increment the iteration variable.
- i++
- }
-
- // Get the correct input stream based on the response code.
- val inputStream: InputStream = if (responseCode == 404) // Get the error stream.
- BufferedInputStream(httpUrlConnection.errorStream)
- else // Get the response body stream.
- BufferedInputStream(httpUrlConnection.inputStream)
-
- // Initialize the byte array output stream and the conversion buffer byte array.
- val byteArrayOutputStream = ByteArrayOutputStream()
- val conversionBufferByteArray = ByteArray(1024)
-
- // Define the buffer length variable.
- var bufferLength: Int
-
- try {
- // 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.
- while (inputStream.read(conversionBufferByteArray).also { bufferLength = it } > 0) { // Proceed while the amount of data stored in the buffer is > 0.
- // Write the contents of the conversion buffer to the byte array output stream.
- byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength)
- }
- } catch (exception: IOException) {
- // Return the error message.
- webViewSource.returnError(exception.toString())
- }
-
- // Close the input stream.
- inputStream.close()
-
- // Populate the response body string with the contents of the byte array output stream.
- responseBodyBuilder.append(byteArrayOutputStream.toString())
- } finally {
- // Disconnect HTTP URL connection.
- httpUrlConnection.disconnect()
- }
- } catch (exception: Exception) {
- // Return the error message.
- webViewSource.returnError(exception.toString())
- }
- }
-
- // Return the spannable string builders.
- return arrayOf(requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder)
- }
-}
\ No newline at end of file