]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/helpers/CheckFilterListHelper.kt
Release 3.20.
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / helpers / CheckFilterListHelper.kt
1
2 /* SPDX-License-Identifier: GPL-3.0-or-later
3  * SPDX-FileCopyrightText: 2018-2019, 2021-2023, 2025-2026 Soren Stoutner <soren@stoutner.com>
4  *
5  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android/>.
6  *
7  * This program is free software: you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License as published by the Free Software
9  * Foundation, either version 3 of the License, or (at your option) any later
10  * version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
15  * details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program.  If not, see <https://www.gnu.org/licenses/>.
19  */
20
21 package com.stoutner.privacybrowser.helpers
22
23 import android.util.Log
24 import com.stoutner.privacybrowser.dataclasses.FilterListDataClass
25 import com.stoutner.privacybrowser.dataclasses.FilterListEntryDataClass
26 import com.stoutner.privacybrowser.dataclasses.FilterOptionDisposition
27 import com.stoutner.privacybrowser.dataclasses.MatchedUrlType
28 import com.stoutner.privacybrowser.dataclasses.RequestDataClass
29 import com.stoutner.privacybrowser.dataclasses.RequestDisposition
30 import com.stoutner.privacybrowser.dataclasses.Sublist
31
32 import java.util.regex.Pattern
33
34
35 // The public filter list data classes.
36 var easyListDataClass = FilterListDataClass()
37 var easyPrivacyDataClass = FilterListDataClass()
38 var fanboysAnnoyanceDataClass: FilterListDataClass? = null  // This is nullable so that the app can check to see if it has been restarted.
39 var ultraListDataClass = FilterListDataClass()
40 var ultraPrivacyDataClass = FilterListDataClass()
41
42 class CheckFilterListHelper {
43     fun checkFilterList(requestDataClass: RequestDataClass, filterListDataClass: FilterListDataClass): Boolean {
44         // Create a continue checking tracker.  If the tracker changes to false, all further checking of the request will be bypassed.
45         var continueChecking = true
46
47         // Check the main allow list.
48         for (filterListEntryDataClass in filterListDataClass.mainAllowList) {
49             // Check the applied entry against the request URL.
50             continueChecking = checkAppliedEntry(requestDataClass, MatchedUrlType.Url, filterListEntryDataClass)
51
52             // Check the applied entry against the request URL with separators.
53             if (continueChecking)
54                 continueChecking = checkAppliedEntry(requestDataClass, MatchedUrlType.UrlWithSeparators, filterListEntryDataClass)
55
56             // Exit the loop if checking should no longer be continued.
57             if (!continueChecking)
58                 break
59         }
60
61         // Check the initial domain allow list unless a match has already been found.
62         if (continueChecking) {
63             // Check the initial domain allow list.
64             for (filterListEntryDataClass in filterListDataClass.initialDomainAllowList) {
65                 // Check the applied entry against the truncated URL.
66                 continueChecking = checkAppliedEntry(requestDataClass, MatchedUrlType.TruncatedUrl, filterListEntryDataClass)
67
68                 //Check the applied entry against the truncated URL with separators.
69                 if (continueChecking)
70                     continueChecking = checkAppliedEntry(requestDataClass, MatchedUrlType.TruncatedUrlWithSeparators, filterListEntryDataClass)
71
72                 // Exit the loop if checking should no longer be continued.
73                 if (!continueChecking)
74                     break
75             }
76         }
77
78         // Check the regular expression allow list unless a match has already been found.
79         if (continueChecking) {
80             // Check the regular expression allow list.
81             for (filterListEntryDataClass in filterListDataClass.regularExpressionAllowList) {
82                 // Check the applied entries.
83                 continueChecking = checkRegularExpression(requestDataClass, filterListEntryDataClass)
84
85                 // Exit the loop if checking should no longer be continued.
86                 if (!continueChecking)
87                     break
88             }
89         }
90
91         // Check the main block list unless a match has already been found.
92         if (continueChecking) {
93             // Check the main block list.
94             for (filterListEntryDataClass in filterListDataClass.mainBlockList) {
95                 // Check the applied entry against the request URL.
96                 continueChecking = checkAppliedEntry(requestDataClass, MatchedUrlType.Url, filterListEntryDataClass)
97
98                 // Check the applied entry against the request URL with separators.
99                 if (continueChecking)
100                     continueChecking = checkAppliedEntry(requestDataClass, MatchedUrlType.UrlWithSeparators, filterListEntryDataClass)
101
102                 // Exit the loop if checking should no longer be continued.
103                 if (!continueChecking)
104                     break
105             }
106         }
107
108         // Check the initial domain block list unless a match has already been found.
109         if (continueChecking) {
110             // Check the initial domain block list.
111             for (filterListEntryDataClass in filterListDataClass.initialDomainBlockList) {
112                 // Check the applied entry against the truncated URL.
113                 continueChecking = checkAppliedEntry(requestDataClass, MatchedUrlType.TruncatedUrl, filterListEntryDataClass)
114
115                 // Check the applied entry against the truncated URL with separators.
116                 if (continueChecking)
117                     continueChecking = checkAppliedEntry(requestDataClass, MatchedUrlType.TruncatedUrlWithSeparators, filterListEntryDataClass)
118
119                 // Exit the loop if checking should no longer be continued.
120                 if (!continueChecking)
121                     break
122             }
123         }
124
125         // Check the regular expression block list unless a match has already been found.
126         if (continueChecking) {
127             // Check the regular expression block list.
128             for (filterListEntryDataClass in filterListDataClass.regularExpressionBlockList) {
129                 // Check the applied entries.
130                 continueChecking = checkRegularExpression(requestDataClass, filterListEntryDataClass)
131
132                 // Exit the loop if checking should no longer be continued.
133                 if (!continueChecking)
134                     break
135             }
136         }
137
138         // Return the continue checking status.
139         return continueChecking
140     }
141
142     private fun checkAppliedEntry(requestDataClass : RequestDataClass, matchedUrlType : MatchedUrlType, filterListEntryDataClass : FilterListEntryDataClass) : Boolean {
143         // Populate the URL string according to the matched URL type.
144         var urlString = when (matchedUrlType) {
145             MatchedUrlType.Url -> requestDataClass.requestUrlString
146             MatchedUrlType.UrlWithSeparators -> requestDataClass.requestUrlWithSeparatorsString
147             MatchedUrlType.TruncatedUrl -> requestDataClass.truncatedRequestUrlString
148             MatchedUrlType.TruncatedUrlWithSeparators -> requestDataClass.truncatedRequestUrlWithSeparatorsString
149         }
150
151         // Check the entries according to the number.
152         if (filterListEntryDataClass.singleAppliedEntry) {  // There is a single entry.
153             // Process the initial and final matches.
154             if (filterListEntryDataClass.initialMatch && filterListEntryDataClass.finalMatch) {  // This is both an initial and final match.
155                 // Check the URL against the applied entry.
156                 if (urlString == filterListEntryDataClass.appliedEntryList[0]) {
157                     // Set the matched URL type.
158                     requestDataClass.matchedUrlType = matchedUrlType
159
160                     // Check the domain status.
161                     return checkDomain(requestDataClass, filterListEntryDataClass)
162                 }
163             } else if (filterListEntryDataClass.initialMatch) {  // This is an initial match.
164                 // Check the URL against the applied entry.
165                 if (urlString.startsWith(filterListEntryDataClass.appliedEntryList[0])) {
166                     // Set the matched URL type.
167                     requestDataClass.matchedUrlType = matchedUrlType
168
169                     // Check the domain status.
170                     return checkDomain(requestDataClass, filterListEntryDataClass)
171                 }
172             } else if (filterListEntryDataClass.finalMatch) {  // This is a final match.
173                 // Check the URL against the applied entry.
174                 if (urlString.endsWith(filterListEntryDataClass.appliedEntryList[0])) {
175                     // Set the matched URL type.
176                     requestDataClass.matchedUrlType = matchedUrlType
177
178                     // Check the domain status.
179                     return checkDomain(requestDataClass, filterListEntryDataClass)
180                 }
181             } else {  // There is no initial or final matching.
182                 // Check if the URL string contains the applied entry.
183                 if (urlString.contains(filterListEntryDataClass.appliedEntryList[0])) {
184                     // Set the matches URL type.
185                     requestDataClass.matchedUrlType = matchedUrlType
186
187                     return checkDomain(requestDataClass, filterListEntryDataClass)
188                 }
189             }
190         } else {  // There are multiple entries.
191             // Create a URL matches flag.
192             var urlMatches = true
193
194             // Process initial and final matches.
195             if (filterListEntryDataClass.initialMatch && filterListEntryDataClass.finalMatch) {  // This is both an initial and final match.
196                 // Check the URL.
197                 if (urlString.startsWith(filterListEntryDataClass.appliedEntryList[0])) {
198                     // Get the number of characters to remove from the front of the URL string.
199                     val charactersToRemove = filterListEntryDataClass.appliedEntryList[0].length
200
201                     // Remove the entry from the front of the modified URL string.
202                     urlString = urlString.substring(charactersToRemove)
203
204                     // Get the entry locations.
205                     val penultimateEntryNumber = (filterListEntryDataClass.sizeOfAppliedEntryList - 1)
206                     val ultimateEntryIndex: Int = penultimateEntryNumber
207
208                     // Check all the middle entries.
209                     for (i in 0 until penultimateEntryNumber) {
210                         // Get the applied entry index, which will be `-1` if it doesn't exist.
211                         val appliedEntryIndex = urlString.indexOf(filterListEntryDataClass.appliedEntryList[i])
212
213                         // Check if the entry was found.
214                         if (appliedEntryIndex >= 0) {  // The entry is contained in the URL string.
215                             // Get the number of characters to remove from teh from of the URL string.
216                             val charactersToRemove = (appliedEntryIndex + filterListEntryDataClass.appliedEntryList[i].length)
217
218                             // Remove the entry from the fron of the modified URL string.
219                             urlString = urlString.substring(charactersToRemove)
220                         } else {  // The entry is not contained in the URL string.
221                             // Mark the URL matches flag as false.
222                             urlMatches = false
223                         }
224                     }
225
226                     // Check the final entry if the URL still matches.
227                     if (urlMatches) {
228                         // Check if the URL string ends with the last applied entry.
229                         if (urlString.endsWith(filterListEntryDataClass.appliedEntryList[ultimateEntryIndex])) {
230                             // Set the matched URL type.
231                             requestDataClass.matchedUrlType = matchedUrlType
232
233                             // Check the domain status.
234                             return checkDomain(requestDataClass, filterListEntryDataClass)
235                         }
236                     }
237                 }
238             } else if (filterListEntryDataClass.initialMatch) {  // This is an initial match.
239                 // Check the URL.
240                 if (urlString.startsWith(filterListEntryDataClass.appliedEntryList[0])) {
241                     // Get the number of characters to remove from the front of the URL string.
242                     val charactersToRemove = filterListEntryDataClass.appliedEntryList[0].length
243
244                     // Remove the entry from the front of the modified URL string.
245                     urlString = urlString.substring(charactersToRemove)
246
247                     // Check the rest of the entries.
248                     for (i in 1 until filterListEntryDataClass.sizeOfAppliedEntryList) {
249                         // Get the applied entry index, which will be `-1` if it doesn't exist.
250                         val appliedEntryIndex = urlString.indexOf(filterListEntryDataClass.appliedEntryList[i])
251
252                         // Check if the entry was found.
253                         if (appliedEntryIndex >= 0) {  // The entry is contained in the URL string.
254                             // Get the number of characters to remove from the front of the URL string.
255                             val charactersToRemove = appliedEntryIndex + filterListEntryDataClass.appliedEntryList[i].length
256
257                             // Remove the entry from the front of the modified URL string.
258                             urlString = urlString.substring(charactersToRemove)
259                         } else {  // The entry is not contained in the URL string.
260                             // Mark the URL matches flag as false.
261                             urlMatches = false
262                         }
263                     }
264
265                     // Check the domain status if the URL still matches.
266                     if (urlMatches) {
267                         // Set the matched URL type.
268                         requestDataClass.matchedUrlType = matchedUrlType
269
270                         // Check the domain status.
271                         return checkDomain(requestDataClass, filterListEntryDataClass)
272                     }
273                 }
274             } else if (filterListEntryDataClass.finalMatch) {  // This is a final match.
275                 // Get the entry locations.
276                 val penultimateEntryNumber= (filterListEntryDataClass.sizeOfAppliedEntryList - 1)
277                 val ultimateEntryIndex: Int = penultimateEntryNumber
278
279                 // Check all the entries except the last one.
280                 for (i in 0 until penultimateEntryNumber - 1) {
281                     // Get the applied entry index, which will be `-1` if it doesn't exist.
282                     val appliedEntryIndex =urlString.indexOf(filterListEntryDataClass.appliedEntryList[i])
283
284                     // Check if the entry was found.
285                     if (appliedEntryIndex >= 0) {  // The entry is contained in the URL string.
286                         // Get the number of characters to remove from teh front of the URL string.
287                         val charactersToRemove = (appliedEntryIndex + filterListEntryDataClass.appliedEntryList[i].length)
288
289                         // Remove the entry from the front of the modified URL string.
290                         urlString = urlString.substring(charactersToRemove)
291                     } else {  // The entry is not contained in the URL string.
292                         // Mark the URL matches flag as false.
293                         urlMatches = false
294                     }
295                 }
296
297                 // Check the final entry if the URL still matches.
298                 if (urlMatches) {
299                     // Check if the URL string ends with the last applied entry.
300                     if (urlString.endsWith(filterListEntryDataClass.appliedEntryList[ultimateEntryIndex])) {
301                         // Set the matched URL type.
302                         requestDataClass.matchedUrlType = matchedUrlType
303
304                         // Check the domain status.
305                         return checkDomain(requestDataClass, filterListEntryDataClass)
306                     }
307                 }
308             } else {  // There is no initial or final matching.
309                 // Check the URL.
310                 for (i in 0 until filterListEntryDataClass.sizeOfAppliedEntryList) {
311                     // Get the applied entry index, which will be `-1` if it doesn't exist.
312                     val appliedEntryIndex = urlString.indexOf(filterListEntryDataClass.appliedEntryList[i])
313
314                     // Check if the entry was found.
315                     if (appliedEntryIndex >= 0) {  // The entry is contained in the URL string.
316                         // Get the number of characters to remove from the front of the URL string.
317                         val charactersToRemove = (appliedEntryIndex + filterListEntryDataClass.appliedEntryList[i].length)
318
319                         // Remove the entry from the front of the modified URL string.
320                         urlString = urlString.substring(charactersToRemove)
321                     } else {  // The entry is not contained in the URL string.
322                         // Mark the URL matches flag as false.
323                         urlMatches = false
324                     }
325                 }
326
327                 // Check the domain status if the URL still matches.
328                 if (urlMatches) {
329                     // Set the matched URL type.
330                     requestDataClass.matchedUrlType = matchedUrlType
331
332                     // Check the domain status.
333                     return checkDomain(requestDataClass, filterListEntryDataClass)
334                 }
335             }
336         }
337
338         // The applied entry doesn't match.  Return `true` to continue processing the URL request.
339         return true
340     }
341
342     private fun checkRegularExpression(requestDataClass : RequestDataClass, filterListEntryDataClass : FilterListEntryDataClass) : Boolean {
343         // Create an applied entry regular expression pattern.
344         val appliedEntryRegularExpressionPattern = Pattern.compile(filterListEntryDataClass.appliedEntryList[0])
345
346         // Check if the regular expression matches the URL string.
347         val regularExpressionMatcher = appliedEntryRegularExpressionPattern.matcher(requestDataClass.requestUrlString)
348
349         // Check the domain status if the regular expression matches.
350         return if (regularExpressionMatcher.matches())
351             checkDomain(requestDataClass, filterListEntryDataClass)
352         else // If the regular expression doesn't match, return `true` to continue processing the URL request.
353             true
354     }
355
356     private fun checkDomain(requestDataClass : RequestDataClass, filterListEntryDataClass : FilterListEntryDataClass) : Boolean {
357         // Check the domain status.
358         if (filterListEntryDataClass.domain == FilterOptionDisposition.Null) {  // Ignore the domain status.
359             // Check the third party status.
360             return checkThirdParty(requestDataClass, filterListEntryDataClass)
361         } else if (filterListEntryDataClass.domain == FilterOptionDisposition.Apply) {  // Block the enumerated domains.
362             // Check each domain.
363             for (blockedDomain in filterListEntryDataClass.domainList) {
364                 // Check if the request came from a blocked domain.
365                 if (requestDataClass.firstPartyHostString.endsWith(blockedDomain)) {
366                     // Check the third-party status.
367                     return checkThirdParty(requestDataClass, filterListEntryDataClass)
368                 }
369             }
370         } else if (filterListEntryDataClass.domain == FilterOptionDisposition.Override) {  // Block domains that are not overridden.
371             // Create a block domain flag.
372             var blockDomain = true
373
374             // Check each overridden domain.
375             for (overriddenDomain in filterListEntryDataClass.domainList) {
376                 // Check if the request came from an overridden domain.
377                 if (requestDataClass.firstPartyHostString.endsWith(overriddenDomain)) {
378                     // Don't block the domain.
379                     blockDomain = false
380                 }
381             }
382
383             // Continue checking if the domain is blocked.
384             if (blockDomain) {
385                 // Check the third-party status.
386                 return checkThirdParty(requestDataClass, filterListEntryDataClass)
387             }
388         }
389
390         // There is a domain specified that doesn't match this request.  Return `true` to continue processing the URL request.
391         return true
392     }
393
394     private fun checkThirdParty(requestDataClass : RequestDataClass, filterListEntryDataClass : FilterListEntryDataClass) : Boolean {
395         // Check the third-party status.
396         return if ((filterListEntryDataClass.thirdParty == FilterOptionDisposition.Null) ||  // Ignore the third-party status.
397             ((filterListEntryDataClass.thirdParty == FilterOptionDisposition.Apply) && requestDataClass.isThirdPartyRequest) ||  // Block third-party requests.
398             ((filterListEntryDataClass.thirdParty == FilterOptionDisposition.Override) && !requestDataClass.isThirdPartyRequest)) // Block first-party requests.
399         {
400             // Process the request.
401             processRequest(requestDataClass, filterListEntryDataClass)
402         } else { // The third-party option specified doesn't match this request.  Return `true` to continue processing the URL request.
403             true
404         }
405     }
406
407     private fun processRequest(requestDataClass : RequestDataClass, filterListEntryDataClass : FilterListEntryDataClass) : Boolean {
408         // Populate the filter list and sublist.
409         requestDataClass.filterList = filterListEntryDataClass.filterList
410         requestDataClass.sublist = filterListEntryDataClass.sublist
411
412         Log.i("FilterList", "Filter List:  ${filterListEntryDataClass.filterList}")
413
414         // Set the request disposition.
415         when (requestDataClass.sublist) {
416             Sublist.MainAllowList -> requestDataClass.disposition = RequestDisposition.Allowed
417             Sublist.InitialDomainAllowList -> requestDataClass.disposition = RequestDisposition.Allowed
418             Sublist.RegularExpressionAllowList -> requestDataClass.disposition = RequestDisposition.Allowed
419             Sublist.MainBlockList -> requestDataClass.disposition = RequestDisposition.Blocked
420             Sublist.InitialDomainBlockList -> requestDataClass.disposition = RequestDisposition.Blocked
421             Sublist.RegularExpressionBlockList -> requestDataClass.disposition = RequestDisposition.Blocked
422         }
423
424         // Populate the filter list entry data class items.
425         requestDataClass.filterListOriginalEntryString = filterListEntryDataClass.originalEntryString
426         requestDataClass.filterListOriginalFilterOptionsString = filterListEntryDataClass.originalFilterOptionsString
427         requestDataClass.filterListAppliedEntryList = filterListEntryDataClass.appliedEntryList
428         requestDataClass.filterListAppliedFilterOptionsList = filterListEntryDataClass.appliedFilterOptionsList
429         requestDataClass.filterListDomainList = filterListEntryDataClass.domainList
430         requestDataClass.filterListFinalMatch = filterListEntryDataClass.finalMatch
431         requestDataClass.filterListInitialMatch = filterListEntryDataClass.initialMatch
432         requestDataClass.filterListSingleAppliedEntry = filterListEntryDataClass.singleAppliedEntry
433         requestDataClass.filterListSizeOfAppliedEntryList = filterListEntryDataClass.sizeOfAppliedEntryList
434         requestDataClass.filterListDomain = filterListEntryDataClass.domain
435         requestDataClass.filterListThirdParty = filterListEntryDataClass.thirdParty
436
437         // Log the disposition.
438         //Log.i("Request processed", "${requestDataClass.requestUrlString} - Filter list entry:  ${requestDataClass.filterListAppliedEntryList}")
439
440         // Returning `false` stops all processing of the request.
441         return false
442     }
443 }