X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Fhelpers%2FUrlHelper.kt;fp=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Fhelpers%2FUrlHelper.kt;h=e51b2c0bf231cbf522c683709c46d43e40744ebc;hp=0000000000000000000000000000000000000000;hb=96fd2b4338ea6dd9520605b2ef290e38ef044f1f;hpb=ade72400eaa718cbd0857440eb4eefa41fed633d diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/UrlHelper.kt b/app/src/main/java/com/stoutner/privacybrowser/helpers/UrlHelper.kt new file mode 100644 index 00000000..e51b2c0b --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/UrlHelper.kt @@ -0,0 +1,261 @@ +/* + * Copyright 2020-2022 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.helpers + +import android.content.Context +import android.net.Uri +import android.webkit.CookieManager +import android.webkit.MimeTypeMap + +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 + + // Only process the content disposition string if it isn't null. + if (contentDispositionString != null) { // The content disposition is not null. + // Check to see if the content disposition contains a file name. + if (contentDispositionString.contains("filename=")) { // The content disposition contains a filename. + // Get the part of the content disposition after `filename=`. + fileNameString = contentDispositionString.substring(contentDispositionString.indexOf("filename=") + 9) + + // Remove any `;` and anything after it. This removes any entries after the filename. + if (fileNameString.contains(";")) + fileNameString = fileNameString.substring(0, fileNameString.indexOf(";") - 1) + + // Remove any `"` at the beginning of the string. + if (fileNameString.startsWith("\"")) + fileNameString = fileNameString.substring(1) + + // Remove any `"` at the end of the string. + if (fileNameString.endsWith("\"")) + fileNameString = fileNameString.substring(0, fileNameString.length - 1) + } else { // The headers contain no useful information. + // Get the file name string from the URL. + fileNameString = getFileNameFromUrl(context, urlString, contentTypeString) + } + } else { // The content disposition is null. + // Get the file name string from the URL. + fileNameString = getFileNameFromUrl(context, urlString, contentTypeString) + } + + // Return the file name string. + return fileNameString + } + + private fun getFileNameFromUrl(context: Context, urlString: String, contentTypeString: String?): String { + // Convert the URL string to a URI. + val uri = Uri.parse(urlString) + + // Get the last path segment. + var lastPathSegment = uri.lastPathSegment + + // Use a default file name if the last path segment is null. + if (lastPathSegment == null) { + // Set the last path segment to be the generic file name. + lastPathSegment = context.getString(R.string.file) + + // Add a file extension if it can be detected. + if (MimeTypeMap.getSingleton().hasMimeType(contentTypeString)) + lastPathSegment = lastPathSegment + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(contentTypeString) + } + + // Return the last path segment as the file name. + return lastPathSegment + } + + fun getNameAndSize(context: Context, urlString: String, userAgent: String, cookiesEnabled: Boolean): Pair { + // Define the strings. + var fileNameString: String + var formattedFileSize: String + + // Populate the file size and name strings. + if (urlString.startsWith("data:")) { // The URL contains the entire data of an image. + // Remove `data:` from the beginning of the URL. + val urlWithoutData = urlString.substring(5) + + // Get the URL MIME type, which ends with a `;`. + val urlMimeType = urlWithoutData.substring(0, urlWithoutData.indexOf(";")) + + // Get the Base64 data, which begins after a `,`. + val base64DataString = urlWithoutData.substring(urlWithoutData.indexOf(",") + 1) + + // Calculate the file size of the data URL. Each Base64 character represents 6 bits. + formattedFileSize = NumberFormat.getInstance().format(base64DataString.length * 3L / 4) + " " + context.getString(R.string.bytes) + + // Set the file name according to the MIME type. + fileNameString = context.getString(R.string.file) + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(urlMimeType) + } else { // The URL refers to the location of the data. + // Initialize the formatted file size string. + 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 { + // Convert the URL string to a URL. + val url = URL(urlString) + + // 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()) + + // Add the cookies if they are not null. + if (cookiesString != null) + httpUrlConnection.setRequestProperty("Cookie", cookiesString) + } + + // 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) + + // Set the file name according to the URL. + fileNameString = getFileNameFromUrl(context, urlString, null) + } else { // The response code is not an error message. + // Get the headers. + val contentLengthString = httpUrlConnection.getHeaderField("Content-Length") + val contentDispositionString = httpUrlConnection.getHeaderField("Content-Disposition") + var contentTypeString = httpUrlConnection.contentType + + // Remove anything after the MIME type in the content type string. + if (contentTypeString.contains(";")) + contentTypeString = contentTypeString.substring(0, contentTypeString.indexOf(";")) + + // 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) + } + + // Get the file name string from the content disposition. + fileNameString = getFileName(context, contentDispositionString, contentTypeString, urlString) + } + } 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) + + // Set the file name according to the URL. + fileNameString = getFileNameFromUrl(context, urlString, null) + } + } + + // Return the file name and size. + 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) + } + } + + // 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) + } + + // Return the formatted file size. + return formattedFileSize + } +}