You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iceraven-browser/app/src/main/java/org/mozilla/fenix/home/blocklist/BlocklistHandler.kt

153 lines
5.6 KiB
Kotlin

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.home.blocklist
import android.net.Uri
import androidx.annotation.VisibleForTesting
import mozilla.components.support.ktx.kotlin.sha1
import org.mozilla.fenix.ext.containsQueryParameters
import org.mozilla.fenix.home.recentbookmarks.RecentBookmark
import org.mozilla.fenix.home.recentsyncedtabs.RecentSyncedTabState
import org.mozilla.fenix.home.recenttabs.RecentTab
import org.mozilla.fenix.home.recentvisits.RecentlyVisitedItem
import org.mozilla.fenix.utils.Settings
/**
* Class for interacting with the blocklist stored in [settings].
* The blocklist is a set of SHA1 hashed URLs, which are stripped
* of protocols and common subdomains like "www" or "mobile".
*
* Also used for filtering the sponsored URLs.
*/
class BlocklistHandler(private val settings: Settings) {
/**
* Add an URL to the blocklist. The URL will be stripped and hashed,
* so no pre-formatted is required.
*/
fun addUrlToBlocklist(url: String) {
val updatedBlocklist = settings.homescreenBlocklist + url.stripAndHash()
settings.homescreenBlocklist = updatedBlocklist
}
/**
* Filter a list of recent bookmarks by the blocklist. Requires this class to be contextually
* in a scope.
*/
@JvmName("filterRecentBookmark")
fun List<RecentBookmark>.filteredByBlocklist(): List<RecentBookmark> =
settings.homescreenBlocklist.let { blocklist ->
filterNot {
it.url?.let { url -> blocklistContainsUrl(blocklist, url) } ?: false
}
}
/**
* Filter a list of recent tabs by the blocklist. Requires this class to be contextually
* in a scope.
*/
@JvmName("filterRecentTab")
fun List<RecentTab>.filteredByBlocklist(): List<RecentTab> =
settings.homescreenBlocklist.let { blocklist ->
filterNot {
it is RecentTab.Tab && blocklistContainsUrl(blocklist, it.state.content.url)
}
}
/**
* Filter a list of recent tabs from sponsored urls.
*/
@JvmName("filterContileRecentTab")
fun List<RecentTab>.filterContile(): List<RecentTab> = filterNot {
it is RecentTab.Tab &&
Uri.parse(it.state.content.url).containsQueryParameters(settings.frecencyFilterQuery)
}
/**
* If the state is set to [RecentSyncedTabState.Success], filter the list of recently synced
* tabs by the blocklist. If the filtered list of tabs is empty, change the state to
* [RecentSyncedTabState.None]
*/
@JvmName("filterRecentSyncedTabState")
fun RecentSyncedTabState.filteredByBlocklist() =
if (this is RecentSyncedTabState.Success) {
val filteredTabs = settings.homescreenBlocklist.let { blocklist ->
this.tabs.filterNot {
blocklistContainsUrl(blocklist, it.url)
}
}
if (filteredTabs.isEmpty()) {
RecentSyncedTabState.None
} else {
RecentSyncedTabState.Success(filteredTabs)
}
} else {
this
}
/**
* Filter a list of recently synced tabs of sponsored urls if the state is
* [RecentSyncedTabState.Success].
*/
@JvmName("filterContileRecentSyncedTab")
fun RecentSyncedTabState.filterContile() = if (this is RecentSyncedTabState.Success) {
val filteredTabs = this.tabs.filterNot {
Uri.parse(it.url).containsQueryParameters(settings.frecencyFilterQuery)
}
if (filteredTabs.isEmpty()) {
RecentSyncedTabState.None
} else {
RecentSyncedTabState.Success(filteredTabs)
}
} else {
this
}
/**
* Filter a list of recent history items by the blocklist. Requires this class to be contextually
* in a scope.
*/
@JvmName("filterRecentHistory")
fun List<RecentlyVisitedItem>.filteredByBlocklist(): List<RecentlyVisitedItem> =
settings.homescreenBlocklist.let { blocklist ->
filterNot {
it is RecentlyVisitedItem.RecentHistoryHighlight &&
blocklistContainsUrl(blocklist, it.url)
}
}
/**
* Filter a list of recently visited history items of sponsored urls.
*/
@JvmName("filterContileRecentlyVisited")
fun List<RecentlyVisitedItem>.filterContile(): List<RecentlyVisitedItem> = filterNot {
it is RecentlyVisitedItem.RecentHistoryHighlight &&
Uri.parse(it.url).containsQueryParameters(settings.frecencyFilterQuery)
}
private fun blocklistContainsUrl(blocklist: Set<String>, url: String): Boolean =
blocklist.any { it == url.stripAndHash() }
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun String.stripAndHash(): String =
this.stripProtocolAndCommonSubdomains().sha1()
// Eventually, this should be standardize in A-C and this can then be removed
// https://github.com/mozilla-mobile/android-components/issues/11743
private fun String.stripProtocolAndCommonSubdomains(): String {
val stripped = this.substringAfter("://").dropLastWhile { it == '/' }
// This kind of stripping allows us to match "twitter" to "mobile.twitter.com".
// Borrowed from DomainMatcher in A-C
val domainsToStrip = listOf("www", "mobile", "m")
domainsToStrip.forEach { domain ->
if (stripped.startsWith("$domain.")) {
return stripped.substring(domain.length + 1)
}
}
return stripped
}