]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/helpers/UrlHelper.kt
Update the file name when the URL changes in SaveDialog. https://redmine.stoutner...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / helpers / UrlHelper.kt
1 /*
2  * Copyright 2020-2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser.helpers
21
22 import android.content.Context
23 import android.net.Uri
24 import android.webkit.CookieManager
25 import android.webkit.MimeTypeMap
26
27 import com.stoutner.privacybrowser.R
28
29 import java.lang.Exception
30 import java.net.HttpURLConnection
31 import java.net.URL
32 import java.text.NumberFormat
33
34 object UrlHelper {
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.
37     @JvmStatic
38     fun getFileName(context: Context, contentDispositionString: String?, contentTypeString: String?, urlString: String): String {
39         // Define a file name string.
40         var fileNameString: String
41
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)
48
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)
52
53                 // Remove any `"` at the beginning of the string.
54                 if (fileNameString.startsWith("\""))
55                     fileNameString = fileNameString.substring(1)
56
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)
63             }
64         } else {  // The content disposition is null.
65             // Get the file name string from the URL.
66             fileNameString = getFileNameFromUrl(context, urlString, contentTypeString)
67         }
68
69         // Return the file name string.
70         return fileNameString
71     }
72
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)
76
77         // Get the last path segment.
78         var lastPathSegment = uri.lastPathSegment
79
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)
84
85             // Add a file extension if it can be detected.
86             if (MimeTypeMap.getSingleton().hasMimeType(contentTypeString))
87                 lastPathSegment = lastPathSegment + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(contentTypeString)
88         }
89
90         // Return the last path segment as the file name.
91         return lastPathSegment
92     }
93
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
98
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)
103
104             // Get the URL MIME type, which ends with a `;`.
105             val urlMimeType = urlWithoutData.substring(0, urlWithoutData.indexOf(";"))
106
107             // Get the Base64 data, which begins after a `,`.
108             val base64DataString = urlWithoutData.substring(urlWithoutData.indexOf(",") + 1)
109
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)
112
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)
118
119             // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
120             try {
121                 // Convert the URL string to a URL.
122                 val url = URL(urlString)
123
124                 // Instantiate the proxy helper.
125                 val proxyHelper = ProxyHelper()
126
127                 // Get the current proxy.
128                 val proxy = proxyHelper.getCurrentProxy(context)
129
130                 // Open a connection to the URL.  No data is actually sent at this point.
131                 val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
132
133                 // Add the user agent to the header property.
134                 httpUrlConnection.setRequestProperty("User-Agent", userAgent)
135
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())
140
141                     // Add the cookies if they are not null.
142                     if (cookiesString != null)
143                         httpUrlConnection.setRequestProperty("Cookie", cookiesString)
144                 }
145
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.
147                 try {
148                     // Get the status code.  This initiates a network connection.
149                     val responseCode = httpUrlConnection.responseCode
150
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)
155
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.
159                         // Get the headers.
160                         val contentLengthString = httpUrlConnection.getHeaderField("Content-Length")
161                         val contentDispositionString = httpUrlConnection.getHeaderField("Content-Disposition")
162                         var contentTypeString = httpUrlConnection.contentType
163
164                         // Remove anything after the MIME type in the content type string.
165                         if (contentTypeString.contains(";"))
166                             contentTypeString = contentTypeString.substring(0, contentTypeString.indexOf(";"))
167
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()
172
173                             // Format the file size.
174                             formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes)
175                         }
176
177                         // Get the file name string from the content disposition.
178                         fileNameString = getFileName(context, contentDispositionString, contentTypeString, urlString)
179                     }
180                 } finally {
181                     // Disconnect the HTTP URL connection.
182                     httpUrlConnection.disconnect()
183                 }
184             } catch (exception: Exception) {
185                 // Set the formatted file size to indicate a bad URL.
186                 formattedFileSize = context.getString(R.string.invalid_url)
187
188                 // Set the file name according to the URL.
189                 fileNameString = getFileNameFromUrl(context, urlString, null)
190             }
191         }
192
193         // Return the file name and size.
194         return Pair(fileNameString, formattedFileSize)
195     }
196
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)
200
201         // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch exceptions.
202         try {
203             // Instantiate the proxy helper.
204             val proxyHelper = ProxyHelper()
205
206             // Get the current proxy.
207             val proxy = proxyHelper.getCurrentProxy(context)
208
209             // Open a connection to the URL.  No data is actually sent at this point.
210             val httpUrlConnection = url.openConnection(proxy) as HttpURLConnection
211
212             // Add the user agent to the header property.
213             httpUrlConnection.setRequestProperty("User-Agent", userAgent)
214
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())
219
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)
224                 }
225             }
226
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.
228             try {
229                 // Get the status code.  This initiates a network connection.
230                 val responseCode = httpUrlConnection.responseCode
231
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")
239
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()
244
245                         // Format the file size.
246                         formattedFileSize = NumberFormat.getInstance().format(fileSize) + " " + context.getString(R.string.bytes)
247                     }
248                 }
249             } finally {
250                 // Disconnect the HTTP URL connection.
251                 httpUrlConnection.disconnect()
252             }
253         } catch (exception: Exception) {
254             // Set the formatted file size to indicate a bad URL.
255             formattedFileSize = context.getString(R.string.invalid_url)
256         }
257
258         // Return the formatted file size.
259         return formattedFileSize
260     }
261 }