2 * Copyright 2020-2022 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.helpers
22 import android.content.Context
23 import android.net.Uri
24 import android.webkit.CookieManager
25 import android.webkit.MimeTypeMap
27 import com.stoutner.privacybrowser.R
29 import java.lang.Exception
30 import java.net.HttpURLConnection
32 import java.text.NumberFormat
35 // Content dispositions can contain other text besides the file name, and they can be in any order.
36 // Elements are separated by semicolons. Sometimes the file names are contained in quotes.
38 fun getFileName(context: Context, contentDispositionString: String?, contentTypeString: String?, urlString: String): String {
39 // Define a file name string.
40 var fileNameString: String
42 // Only process the content disposition string if it isn't null.
43 if (contentDispositionString != null) { // The content disposition is not null.
44 // Check to see if the content disposition contains a file name.
45 if (contentDispositionString.contains("filename=")) { // The content disposition contains a filename.
46 // Get the part of the content disposition after `filename=`.
47 fileNameString = contentDispositionString.substring(contentDispositionString.indexOf("filename=") + 9)
49 // Remove any `;` and anything after it. This removes any entries after the filename.
50 if (fileNameString.contains(";"))
51 fileNameString = fileNameString.substring(0, fileNameString.indexOf(";") - 1)
53 // Remove any `"` at the beginning of the string.
54 if (fileNameString.startsWith("\""))
55 fileNameString = fileNameString.substring(1)
57 // Remove any `"` at the end of the string.
58 if (fileNameString.endsWith("\""))
59 fileNameString = fileNameString.substring(0, fileNameString.length - 1)
60 } else { // The headers contain no useful information.
61 // Get the file name string from the URL.
62 fileNameString = getFileNameFromUrl(context, urlString, contentTypeString)
64 } else { // The content disposition is null.
65 // Get the file name string from the URL.
66 fileNameString = getFileNameFromUrl(context, urlString, contentTypeString)
69 // Return the file name string.
73 private fun getFileNameFromUrl(context: Context, urlString: String, contentTypeString: String?): String {
74 // Convert the URL string to a URI.
75 val uri = Uri.parse(urlString)
77 // Get the last path segment.
78 var lastPathSegment = uri.lastPathSegment
80 // Use a default file name if the last path segment is null.
81 if (lastPathSegment == null) {
82 // Set the last path segment to be the generic file name.
83 lastPathSegment = context.getString(R.string.file)
85 // Add a file extension if it can be detected.
86 if (MimeTypeMap.getSingleton().hasMimeType(contentTypeString))
87 lastPathSegment = lastPathSegment + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(contentTypeString)
90 // Return the last path segment as the file name.
91 return lastPathSegment
94 fun getNameAndSize(context: Context, urlString: String, userAgent: String, cookiesEnabled: Boolean): Pair<String, String> {
95 // Define the strings.
96 var fileNameString: String
97 var formattedFileSize: String
99 // Populate the file size and name strings.
100 if (urlString.startsWith("data:")) { // The URL contains the entire data of an image.
101 // Remove `data:` from the beginning of the URL.
102 val urlWithoutData = urlString.substring(5)
104 // Get the URL MIME type, which ends with a `;`.
105 val urlMimeType = urlWithoutData.substring(0, urlWithoutData.indexOf(";"))
107 // Get the Base64 data, which begins after a `,`.
108 val base64DataString = urlWithoutData.substring(urlWithoutData.indexOf(",") + 1)
110 // Calculate the file size of the data URL. Each Base64 character represents 6 bits.
111 formattedFileSize = NumberFormat.getInstance().format(base64DataString.length * 3L / 4) + " " + context.getString(R.string.bytes)
113 // Set the file name according to the MIME type.
114 fileNameString = context.getString(R.string.file) + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(urlMimeType)
115 } else { // The URL refers to the location of the data.
116 // Initialize the formatted file size string.
117 formattedFileSize = context.getString(R.string.unknown_size)
119 // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
121 // Convert the URL string to a URL.
122 val url = URL(urlString)
124 // Instantiate the proxy helper.
125 val proxyHelper = ProxyHelper()
127 // Get the current proxy.
128 val proxy = proxyHelper.getCurrentProxy(context)
130 // Open a connection to the URL. No data is actually sent at this point.
131 val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
133 // Add the user agent to the header property.
134 httpUrlConnection.setRequestProperty("User-Agent", userAgent)
136 // Add the cookies if they are enabled.
137 if (cookiesEnabled) {
138 // Get the cookies for the current domain.
139 val cookiesString = CookieManager.getInstance().getCookie(url.toString())
141 // Add the cookies if they are not null.
142 if (cookiesString != null)
143 httpUrlConnection.setRequestProperty("Cookie", cookiesString)
146 // 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.
148 // Get the status code. This initiates a network connection.
149 val responseCode = httpUrlConnection.responseCode
151 // Check the response code.
152 if (responseCode >= 400) { // The response code is an error message.
153 // Set the formatted file size to indicate a bad URL.
154 formattedFileSize = context.getString(R.string.invalid_url)
156 // Set the file name according to the URL.
157 fileNameString = getFileNameFromUrl(context, urlString, null)
158 } else { // The response code is not an error message.
160 val contentLengthString = httpUrlConnection.getHeaderField("Content-Length")
161 val contentDispositionString = httpUrlConnection.getHeaderField("Content-Disposition")
162 var contentTypeString = httpUrlConnection.contentType
164 // Remove anything after the MIME type in the content type string.
165 if (contentTypeString.contains(";"))
166 contentTypeString = contentTypeString.substring(0, contentTypeString.indexOf(";"))
168 // Only process the content length string if it isn't null.
169 if (contentLengthString != null) {
170 // Convert the content length string to a long.
171 val fileSize = contentLengthString.toLong()
173 // Format the file size.
174 formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes)
177 // Get the file name string from the content disposition.
178 fileNameString = getFileName(context, contentDispositionString, contentTypeString, urlString)
181 // Disconnect the HTTP URL connection.
182 httpUrlConnection.disconnect()
184 } catch (exception: Exception) {
185 // Set the formatted file size to indicate a bad URL.
186 formattedFileSize = context.getString(R.string.invalid_url)
188 // Set the file name according to the URL.
189 fileNameString = getFileNameFromUrl(context, urlString, null)
193 // Return the file name and size.
194 return Pair(fileNameString, formattedFileSize)
197 fun getSize(context: Context, url: URL, userAgent: String, cookiesEnabled: Boolean): String {
198 // Initialize the formatted file size string.
199 var formattedFileSize = context.getString(R.string.unknown_size)
201 // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
203 // Instantiate the proxy helper.
204 val proxyHelper = ProxyHelper()
206 // Get the current proxy.
207 val proxy = proxyHelper.getCurrentProxy(context)
209 // Open a connection to the URL. No data is actually sent at this point.
210 val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
212 // Add the user agent to the header property.
213 httpUrlConnection.setRequestProperty("User-Agent", userAgent)
215 // Add the cookies if they are enabled.
216 if (cookiesEnabled) {
217 // Get the cookies for the current domain.
218 val cookiesString = CookieManager.getInstance().getCookie(url.toString())
220 // Only add the cookies if they are not null.
221 if (cookiesString != null) {
222 // Add the cookies to the header property.
223 httpUrlConnection.setRequestProperty("Cookie", cookiesString)
227 // 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.
229 // Get the status code. This initiates a network connection.
230 val responseCode = httpUrlConnection.responseCode
232 // Check the response code.
233 if (responseCode >= 400) { // The response code is an error message.
234 // Set the formatted file size to indicate a bad URL.
235 formattedFileSize = context.getString(R.string.invalid_url)
236 } else { // The response code is not an error message.
237 // Get the content length header.
238 val contentLengthString = httpUrlConnection.getHeaderField("Content-Length")
240 // Only process the content length string if it isn't null.
241 if (contentLengthString != null) {
242 // Convert the content length string to a long.
243 val fileSize = contentLengthString.toLong()
245 // Format the file size.
246 formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes)
250 // Disconnect the HTTP URL connection.
251 httpUrlConnection.disconnect()
253 } catch (exception: Exception) {
254 // Set the formatted file size to indicate a bad URL.
255 formattedFileSize = context.getString(R.string.invalid_url)
258 // Return the formatted file size.
259 return formattedFileSize