diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
index 8e374db5e616..3a04cb617242 100644
--- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
+++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
@@ -583,6 +583,9 @@ class BrowserTabFragment :
private val errorView
get() = binding.includeErrorView
+ private val warningView
+ get() = binding.maliciousSiteWarningLayout
+
private val sslErrorView
get() = binding.sslErrorWarningLayout
@@ -1371,6 +1374,26 @@ class BrowserTabFragment :
errorView.errorLayout.show()
}
+ private fun showWarning(url: Uri) {
+ webViewContainer.gone()
+ newBrowserTab.newTabLayout.gone()
+ newBrowserTab.newTabContainerLayout.gone()
+ sslErrorView.gone()
+ // TODO (cbarreiro) we should probably define a new view mode
+ omnibar.setViewMode(Omnibar.ViewMode.Error)
+ webView?.onPause()
+ webView?.hide()
+ warningView.bind(/*handler, errorResponse*/) /*{ action ->
+ viewModel.onSSLCertificateWarningAction(action, errorResponse.url)
+ }*/
+ warningView.show()
+ // warningView.leaveSiteButton.setOnClickListener {
+ // viewModel.closeCurrentTab()
+ // // TODO (cbarreiro) Fix, not working
+ // renderer.showNewTab()
+ // }
+ }
+
private fun showSSLWarning(
handler: SslErrorHandler,
errorResponse: SslErrorResponse,
@@ -1711,6 +1734,7 @@ class BrowserTabFragment :
)
is Command.WebViewError -> showError(it.errorType, it.url)
+ is Command.WebViewWarningMaliciousSite -> showWarning(it.url)
is Command.SendResponseToJs -> contentScopeScripts.onResponse(it.data)
is Command.SendResponseToDuckPlayer -> duckPlayerScripts.onResponse(it.data)
is Command.WebShareRequest -> webShareRequest.launch(it.data)
@@ -4006,7 +4030,7 @@ class BrowserTabFragment :
viewModel.onCtaShown()
}
- private fun showNewTab() {
+ fun showNewTab() {
newTabPageProvider.provideNewTabPageVersion().onEach { newTabPage ->
if (newBrowserTab.newTabContainerLayout.childCount == 0) {
newBrowserTab.newTabContainerLayout.addView(
diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt
index 4a0e7b4d7797..6bae7c822ca6 100644
--- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt
+++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt
@@ -156,6 +156,7 @@ import com.duckduckgo.app.browser.commands.Command.ShowWebPageTitle
import com.duckduckgo.app.browser.commands.Command.ToggleReportFeedback
import com.duckduckgo.app.browser.commands.Command.WebShareRequest
import com.duckduckgo.app.browser.commands.Command.WebViewError
+import com.duckduckgo.app.browser.commands.Command.WebViewWarningMaliciousSite
import com.duckduckgo.app.browser.commands.NavigationCommand
import com.duckduckgo.app.browser.customtabs.CustomTabPixelNames
import com.duckduckgo.app.browser.duckplayer.DUCK_PLAYER_FEATURE_NAME
@@ -3187,6 +3188,11 @@ class BrowserTabViewModel @Inject constructor(
command.postValue(WebViewError(errorType, url))
}
+ override fun onReceivedMaliciousSiteWarning(url: Uri) {
+ // TODO (cbarreiro): Fire pixel
+ command.postValue(WebViewWarningMaliciousSite(url))
+ }
+
override fun recordErrorCode(
error: String,
url: String,
diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserWebViewClient.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserWebViewClient.kt
index f65ede0c371d..fa592020db65 100644
--- a/app/src/main/java/com/duckduckgo/app/browser/BrowserWebViewClient.kt
+++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserWebViewClient.kt
@@ -79,7 +79,6 @@ import com.duckduckgo.history.api.NavigationHistory
import com.duckduckgo.privacy.config.api.AmpLinks
import com.duckduckgo.subscriptions.api.Subscriptions
import com.duckduckgo.user.agent.api.ClientBrandHintProvider
-import com.google.android.material.snackbar.Snackbar
import java.net.URI
import javax.inject.Inject
import kotlinx.coroutines.*
@@ -159,7 +158,7 @@ class BrowserWebViewClient @Inject constructor(
try {
Timber.v("shouldOverride webViewUrl: ${webView.url} URL: $url")
webViewClientListener?.onShouldOverride()
- if (requestInterceptor.shouldOverrideUrlLoading(webView, url, isForMainFrame)) {
+ if (requestInterceptor.shouldOverrideUrlLoading(webViewClientListener, url, isForMainFrame)) {
return true
}
diff --git a/app/src/main/java/com/duckduckgo/app/browser/WebViewClientListener.kt b/app/src/main/java/com/duckduckgo/app/browser/WebViewClientListener.kt
index f8fb1313a391..b9d5eeb258fe 100644
--- a/app/src/main/java/com/duckduckgo/app/browser/WebViewClientListener.kt
+++ b/app/src/main/java/com/duckduckgo/app/browser/WebViewClientListener.kt
@@ -95,6 +95,7 @@ interface WebViewClientListener {
fun linkOpenedInNewTab(): Boolean
fun isActiveTab(): Boolean
fun onReceivedError(errorType: WebViewErrorResponse, url: String)
+ fun onReceivedMaliciousSiteWarning(url: Uri)
fun recordErrorCode(error: String, url: String)
fun recordHttpErrorCode(statusCode: Int, url: String)
diff --git a/app/src/main/java/com/duckduckgo/app/browser/WebViewRequestInterceptor.kt b/app/src/main/java/com/duckduckgo/app/browser/WebViewRequestInterceptor.kt
index f93ea7ec3947..0b630cb15bca 100644
--- a/app/src/main/java/com/duckduckgo/app/browser/WebViewRequestInterceptor.kt
+++ b/app/src/main/java/com/duckduckgo/app/browser/WebViewRequestInterceptor.kt
@@ -39,7 +39,6 @@ import com.duckduckgo.httpsupgrade.api.HttpsUpgrader
import com.duckduckgo.privacy.config.api.Gpc
import com.duckduckgo.request.filterer.api.RequestFilterer
import com.duckduckgo.user.agent.api.UserAgentProvider
-import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.withContext
import timber.log.Timber
@@ -63,7 +62,7 @@ interface RequestInterceptor {
@WorkerThread
fun shouldOverrideUrlLoading(
- webView: WebView,
+ webViewClientListener: WebViewClientListener?,
url: Uri,
isForMainFrame: Boolean,
): Boolean
@@ -108,9 +107,9 @@ class WebViewRequestInterceptor(
val url: Uri? = request.url
maliciousSiteBlockerWebViewIntegration.shouldIntercept(request, documentUri) {
- handleSiteBlocked(webView)
+ handleSiteBlocked(webViewClientListener, url)
}?.let {
- handleSiteBlocked(webView)
+ handleSiteBlocked(webViewClientListener, url)
return it
}
@@ -179,22 +178,22 @@ class WebViewRequestInterceptor(
return getWebResourceResponse(request, documentUrl, null)
}
- override fun shouldOverrideUrlLoading(webView: WebView, url: Uri, isForMainFrame: Boolean): Boolean {
+ override fun shouldOverrideUrlLoading(webViewClientListener: WebViewClientListener?, url: Uri, isForMainFrame: Boolean): Boolean {
if (maliciousSiteBlockerWebViewIntegration.shouldOverrideUrlLoading(
url,
isForMainFrame,
) {
- handleSiteBlocked(webView)
+ handleSiteBlocked(webViewClientListener, url)
}
) {
- handleSiteBlocked(webView)
+ handleSiteBlocked(webViewClientListener, url)
return true
}
return false
}
- private fun handleSiteBlocked(webView: WebView) {
- Snackbar.make(webView, "Site blocked", Snackbar.LENGTH_SHORT).show()
+ private fun handleSiteBlocked(webViewClientListener: WebViewClientListener?, url: Uri?) {
+ url?.let { webViewClientListener?.onReceivedMaliciousSiteWarning(it) }
}
private fun getWebResourceResponse(
diff --git a/app/src/main/java/com/duckduckgo/app/browser/commands/Command.kt b/app/src/main/java/com/duckduckgo/app/browser/commands/Command.kt
index 911f4d3e2b92..8638efba276a 100644
--- a/app/src/main/java/com/duckduckgo/app/browser/commands/Command.kt
+++ b/app/src/main/java/com/duckduckgo/app/browser/commands/Command.kt
@@ -217,6 +217,10 @@ sealed class Command {
val url: String,
) : Command()
+ data class WebViewWarningMaliciousSite(
+ val url: Uri,
+ ) : Command()
+
// TODO (cbarreiro) Rename to SendResponseToCSS
data class SendResponseToJs(val data: JsCallbackData) : Command()
data class SendResponseToDuckPlayer(val data: JsCallbackData) : Command()
diff --git a/app/src/main/java/com/duckduckgo/app/browser/webview/MaliciousSiteBlockedWarningLayout.kt b/app/src/main/java/com/duckduckgo/app/browser/webview/MaliciousSiteBlockedWarningLayout.kt
new file mode 100644
index 000000000000..0422ae1ef50d
--- /dev/null
+++ b/app/src/main/java/com/duckduckgo/app/browser/webview/MaliciousSiteBlockedWarningLayout.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2024 DuckDuckGo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.duckduckgo.app.browser.webview
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.FrameLayout
+import com.duckduckgo.app.browser.databinding.ViewMaliciousSiteBlockedWarningBinding
+import com.duckduckgo.common.ui.view.gone
+import com.duckduckgo.common.ui.view.show
+import com.duckduckgo.common.ui.viewbinding.viewBinding
+
+class MaliciousSiteBlockedWarningLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0,
+) : FrameLayout(context, attrs, defStyle) {
+
+ sealed class Action {
+ data object VisitSite : Action()
+ data object LeaveSite : Action()
+ }
+
+ private val binding: ViewMaliciousSiteBlockedWarningBinding by viewBinding()
+
+ fun bind() {
+ resetViewState()
+
+ with(binding) {
+ setListeners()
+ }
+ }
+
+ private fun resetViewState() {
+ with(binding) {
+ advancedCTA.show()
+ advancedGroup.gone()
+ }
+ }
+
+ private fun setListeners() {
+ with(binding) {
+ advancedCTA.setOnClickListener {
+ advancedCTA.gone()
+ advancedGroup.show()
+ errorLayout.post {
+ errorLayout.fullScroll(View.FOCUS_DOWN)
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/malware_site_128.xml b/app/src/main/res/drawable/malware_site_128.xml
new file mode 100644
index 000000000000..cb82a5e50e53
--- /dev/null
+++ b/app/src/main/res/drawable/malware_site_128.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_browser_tab.xml b/app/src/main/res/layout/fragment_browser_tab.xml
index 6a1ec794923c..7d0d6447093e 100644
--- a/app/src/main/res/layout/fragment_browser_tab.xml
+++ b/app/src/main/res/layout/fragment_browser_tab.xml
@@ -71,6 +71,12 @@
android:layout_height="match_parent"
android:visibility="gone" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_malicious_site_blocked_warning.xml b/app/src/main/res/layout/view_malicious_site_blocked_warning.xml
new file mode 100644
index 000000000000..d0b7741f8deb
--- /dev/null
+++ b/app/src/main/res/layout/view_malicious_site_blocked_warning.xml
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e5016e54ca41..3cfc402b4a51 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -720,6 +720,12 @@
The security certificate for %1$s does not match *.%2$s. It’s possible that the website is misconfigured or that an attacker has compromised your connection, or that your system clock is incorrect.
The security certificate for %1$s is not trusted by your device\'s operating system. It’s possible that the website is misconfigured or that an attacker has compromised your connection, or that your system clock is incorrect.
+
+ Warning: This site may put your personal information at risk
+ DuckDuckGo blocked this page because it may be distributing malware designed to compromise your device or steal your personal information. Learn more
+ Leave This Site
+ Advanced
+ If you believe this website is safe, you can report an error. You can still visit the website at your own risk.\n\nAccept Risk and Visit Site
Ready for a better, more private internet?]]>
Let\'s do it!
diff --git a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/data/MaliciousSiteRepository.kt b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/data/MaliciousSiteRepository.kt
index 8e0468fde422..cd20e693285a 100644
--- a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/data/MaliciousSiteRepository.kt
+++ b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/data/MaliciousSiteRepository.kt
@@ -97,7 +97,7 @@ class RealMaliciousSiteRepository @Inject constructor(
override suspend fun matches(hashPrefix: String): List {
return try {
maliciousSiteService.getMatches(hashPrefix).matches.also {
- Timber.d("\uD83D\uDFE2 Cris: Fetched $it matches for hash prefix $hashPrefix")
+ Timber.d("\uD83D\uDFE2 Cris: hash prefix $hashPrefix. Fetched $it matches")
}
} catch (e: Exception) {
Timber.d("\uD83D\uDD34 Cris: Failed to fetch matches for hash prefix $hashPrefix")
diff --git a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/data/network/MaliciousSiteService.kt b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/data/network/MaliciousSiteService.kt
index 6f3fd6787c5d..9fcd77271cf1 100644
--- a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/data/network/MaliciousSiteService.kt
+++ b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/data/network/MaliciousSiteService.kt
@@ -57,12 +57,12 @@ data class HashPrefixResponse(
val insert: Set,
val delete: Set,
val revision: Int,
- val replace: Boolean,
+ val replace: String,
)
data class FilterSetResponse(
val insert: Set,
val delete: Set,
val revision: Int,
- val replace: Boolean,
+ val replace: String,
)
diff --git a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/domain/RealMaliciousSiteProtection.kt b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/domain/RealMaliciousSiteProtection.kt
index 87384dc0ffca..72a9851ed888 100644
--- a/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/domain/RealMaliciousSiteProtection.kt
+++ b/malicious-site-protection/malicious-site-protection-impl/src/main/kotlin/com/duckduckgo/malicioussiteprotection/impl/domain/RealMaliciousSiteProtection.kt
@@ -68,7 +68,13 @@ class RealMaliciousSiteProtection @Inject constructor(
}
}
appCoroutineScope.launch(dispatchers.io()) {
- confirmationCallback(matches(hashPrefix, url, hostname, hash))
+ try {
+ val matches = matches(hashPrefix, url, hostname, hash)
+ confirmationCallback(matches)
+ } catch (e: Exception) {
+ Timber.e(e, "\uD83D\uDD34 Cris: shouldBlock $url")
+ confirmationCallback(false)
+ }
}
return IsMaliciousResult.WAIT_FOR_CONFIRMATION
}