/*
- * Copyright 2020-2022 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2020-2023 Soren Stoutner <soren@stoutner.com>.
*
* This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
*
import android.content.Context
import android.net.Uri
+import android.text.Spanned
+import android.text.style.ForegroundColorSpan
import android.webkit.CookieManager
import android.webkit.MimeTypeMap
+import android.widget.EditText
import com.stoutner.privacybrowser.R
-import java.lang.Exception
import java.net.HttpURLConnection
import java.net.URL
import java.text.NumberFormat
object UrlHelper {
// Content dispositions can contain other text besides the file name, and they can be in any order.
// Elements are separated by semicolons. Sometimes the file names are contained in quotes.
- @JvmStatic
fun getFileName(context: Context, contentDispositionString: String?, contentTypeString: String?, urlString: String): String {
// Define a file name string.
var fileNameString: String
return Pair(fileNameString, formattedFileSize)
}
- fun getSize(context: Context, url: URL, userAgent: String, cookiesEnabled: Boolean): String {
- // Initialize the formatted file size string.
- var formattedFileSize = context.getString(R.string.unknown_size)
-
- // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
- try {
- // Instantiate the proxy helper.
- val proxyHelper = ProxyHelper()
-
- // Get the current proxy.
- val proxy = proxyHelper.getCurrentProxy(context)
-
- // Open a connection to the URL. No data is actually sent at this point.
- val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
-
- // Add the user agent to the header property.
- httpUrlConnection.setRequestProperty("User-Agent", userAgent)
-
- // Add the cookies if they are enabled.
- if (cookiesEnabled) {
- // Get the cookies for the current domain.
- val cookiesString = CookieManager.getInstance().getCookie(url.toString())
-
- // Only add the cookies if they are not null.
- if (cookiesString != null) {
- // Add the cookies to the header property.
- httpUrlConnection.setRequestProperty("Cookie", cookiesString)
- }
+ fun highlightSyntax(urlEditText: EditText, initialGrayColorSpan: ForegroundColorSpan, finalGrayColorSpan: ForegroundColorSpan, redColorSpan: ForegroundColorSpan) {
+ // Get the URL string.
+ val urlString: String = urlEditText.text.toString()
+
+ // Highlight the URL according to the protocol.
+ if (urlString.startsWith("file://") || urlString.startsWith("content://")) { // This is a file or content URL.
+ // De-emphasize everything before the file name.
+ urlEditText.text.setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+ } else { // This is a web URL.
+ // Get the index of the `/` immediately after the domain name.
+ val endOfDomainName = urlString.indexOf("/", urlString.indexOf("//") + 2)
+
+ // Get the base URL.
+ val baseUrl: String = if (endOfDomainName > 0) // There is at least one character after the base URL.
+ urlString.substring(0, endOfDomainName)
+ else // There are no characters after the base URL.
+ urlString
+
+ // Get the index of the last `.` in the domain.
+ val lastDotIndex = baseUrl.lastIndexOf(".")
+
+ // Get the index of the penultimate `.` in the domain.
+ val penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1)
+
+ // Markup the beginning of the URL.
+ if (urlString.startsWith("http://")) { // The protocol is not encrypted.
+ // Highlight the protocol in red.
+ urlEditText.text.setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+
+ // De-emphasize subdomains.
+ if (penultimateDotIndex > 0) // There is more than one subdomain in the domain name.
+ urlEditText.text.setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+ } else if (urlString.startsWith("https://")) { // The protocol is encrypted.
+ // De-emphasize the protocol of connections that are encrypted.
+ if (penultimateDotIndex > 0) // There is more than one subdomain in the domain name. De-emphasize the protocol and the additional subdomains.
+ urlEditText.text.setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
+ else // There is only one subdomain in the domain name. De-emphasize only the protocol.
+ urlEditText.text.setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
}
- // 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 status code. This initiates a network connection.
- val responseCode = httpUrlConnection.responseCode
-
- // Check the response code.
- if (responseCode >= 400) { // The response code is an error message.
- // Set the formatted file size to indicate a bad URL.
- formattedFileSize = context.getString(R.string.invalid_url)
- } else { // The response code is not an error message.
- // Get the content length header.
- val contentLengthString = httpUrlConnection.getHeaderField("Content-Length")
-
- // Only process the content length string if it isn't null.
- if (contentLengthString != null) {
- // Convert the content length string to a long.
- val fileSize = contentLengthString.toLong()
-
- // Format the file size.
- formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes)
- }
- }
- } finally {
- // Disconnect the HTTP URL connection.
- httpUrlConnection.disconnect()
- }
- } catch (exception: Exception) {
- // Set the formatted file size to indicate a bad URL.
- formattedFileSize = context.getString(R.string.invalid_url)
+ // De-emphasize the text after the domain name.
+ if (endOfDomainName > 0)
+ urlEditText.text.setSpan(finalGrayColorSpan, endOfDomainName, urlString.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
}
-
- // Return the formatted file size.
- return formattedFileSize
}
}