@@ -24,8 +24,10 @@ import com.duckduckgo.app.browser.webview.ExemptedUrlsHolder.ExemptedUrl
24
24
import com.duckduckgo.app.browser.webview.RealMaliciousSiteBlockerWebViewIntegration.IsMaliciousViewData
25
25
import com.duckduckgo.app.di.AppCoroutineScope
26
26
import com.duckduckgo.app.di.IsMainProcess
27
+ import com.duckduckgo.app.pixels.AppPixelName
27
28
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
28
29
import com.duckduckgo.app.settings.db.SettingsDataStore
30
+ import com.duckduckgo.app.statistics.pixels.Pixel
29
31
import com.duckduckgo.common.utils.DispatcherProvider
30
32
import com.duckduckgo.di.scopes.AppScope
31
33
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection
@@ -61,7 +63,7 @@ interface MaliciousSiteBlockerWebViewIntegration {
61
63
confirmationCallback : (maliciousStatus: MaliciousStatus ) -> Unit ,
62
64
): IsMaliciousViewData
63
65
64
- fun onPageLoadStarted ()
66
+ fun onPageLoadStarted (url : String )
65
67
66
68
fun onSiteExempted (
67
69
url : Uri ,
@@ -97,10 +99,11 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
97
99
@AppCoroutineScope private val appCoroutineScope : CoroutineScope ,
98
100
private val exemptedUrlsHolder : ExemptedUrlsHolder ,
99
101
@IsMainProcess private val isMainProcess : Boolean ,
102
+ private val pixel : Pixel ,
100
103
) : MaliciousSiteBlockerWebViewIntegration, PrivacyConfigCallbackPlugin {
101
104
102
105
@VisibleForTesting(otherwise = VisibleForTesting .PRIVATE )
103
- val processedUrls = mutableListOf <String >()
106
+ val processedUrls = mutableMapOf <String , MaliciousStatus >()
104
107
105
108
private var isFeatureEnabled = false
106
109
private val isSettingEnabled: Boolean
@@ -137,6 +140,7 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
137
140
if (! isEnabled()) {
138
141
return IsMaliciousViewData .Safe
139
142
}
143
+
140
144
val url = request.url.let {
141
145
if (it.fragment != null ) {
142
146
it.buildUpon().fragment(null ).build()
@@ -147,35 +151,49 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
147
151
148
152
val decodedUrl = decodeUrl(url)
149
153
150
- if (processedUrls.contains(decodedUrl)) {
151
- processedUrls.remove(decodedUrl)
152
- Timber .tag(" PhishingAndMalwareDetector" ).d(" Already intercepted, skipping $decodedUrl " )
153
- return IsMaliciousViewData .Safe
154
- }
155
-
156
154
val exemptedUrl = exemptedUrlsHolder.exemptedMaliciousUrls.firstOrNull { it.url.toString() == decodedUrl }
157
155
158
156
if (exemptedUrl != null ) {
159
- Timber .tag( " MaliciousSiteDetector " ). d(" Previously exempted, skipping $decodedUrl as ${exemptedUrl.feed} " )
157
+ Timber .d(" Previously exempted, skipping $decodedUrl as ${exemptedUrl.feed} " )
160
158
return IsMaliciousViewData .MaliciousSite (url, exemptedUrl.feed, true )
161
159
}
162
160
161
+ processedUrls[decodedUrl]?.let {
162
+ processedUrls.remove(decodedUrl)
163
+ Timber .d(" Already intercepted, skipping $decodedUrl , status: $it " )
164
+ return when (it) {
165
+ is Safe -> IsMaliciousViewData .Safe
166
+ is Malicious -> IsMaliciousViewData .MaliciousSite (url, it.feed, false )
167
+ }
168
+ }
169
+
163
170
val belongsToCurrentPage = documentUri?.host == request.requestHeaders[" Referer" ]?.toUri()?.host
164
- if (request.isForMainFrame || (isForIframe(request) && belongsToCurrentPage)) {
165
- when (val result = checkMaliciousUrl(decodedUrl, confirmationCallback)) {
171
+ val isForIframe = isForIframe(request) && belongsToCurrentPage
172
+ if (request.isForMainFrame || isForIframe) {
173
+ val result = checkMaliciousUrl(decodedUrl) {
174
+ if (isForIframe && it is Malicious ) {
175
+ firePixelForMaliciousIframe(it.feed)
176
+ }
177
+ confirmationCallback(it)
178
+ }
179
+ when (result) {
166
180
is ConfirmedResult -> {
181
+ processedUrls[decodedUrl] = result.status
167
182
when (val status = result.status) {
168
183
is Malicious -> {
184
+ if (isForIframe) {
185
+ firePixelForMaliciousIframe(status.feed)
186
+ }
169
187
return IsMaliciousViewData .MaliciousSite (url, status.feed, false )
170
188
}
189
+
171
190
is Safe -> {
172
- processedUrls.add(decodedUrl)
173
191
return IsMaliciousViewData .Safe
174
192
}
175
193
}
176
194
}
195
+
177
196
is WaitForConfirmation -> {
178
- processedUrls.add(decodedUrl)
179
197
return IsMaliciousViewData .WaitForConfirmation
180
198
}
181
199
}
@@ -194,19 +212,22 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
194
212
}
195
213
val decodedUrl = decodeUrl(url)
196
214
197
- if (processedUrls.contains(decodedUrl)) {
198
- processedUrls.remove(decodedUrl)
199
- Timber .tag(" PhishingAndMalwareDetector" ).d(" Already intercepted, skipping $decodedUrl " )
200
- return @runBlocking IsMaliciousViewData .Safe
201
- }
202
-
203
215
val exemptedUrl = exemptedUrlsHolder.exemptedMaliciousUrls.firstOrNull { it.url.toString() == decodedUrl }
204
216
205
217
if (exemptedUrl != null ) {
206
- Timber .tag( " MaliciousSiteDetector " ). d(" Previously exempted, skipping $decodedUrl " )
218
+ Timber .d(" Previously exempted, skipping $decodedUrl " )
207
219
return @runBlocking IsMaliciousViewData .MaliciousSite (url, exemptedUrl.feed, true )
208
220
}
209
221
222
+ processedUrls[decodedUrl]?.let {
223
+ processedUrls.remove(decodedUrl)
224
+ Timber .d(" Already intercepted, skipping $decodedUrl , status: $it " )
225
+ return @runBlocking when (it) {
226
+ is Safe -> IsMaliciousViewData .Safe
227
+ is Malicious -> IsMaliciousViewData .MaliciousSite (url, it.feed, false )
228
+ }
229
+ }
230
+
210
231
// iframes always go through the shouldIntercept method, so we only need to check the main frame here
211
232
if (isForMainFrame) {
212
233
when (val result = checkMaliciousUrl(decodedUrl, confirmationCallback)) {
@@ -216,13 +237,12 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
216
237
return @runBlocking IsMaliciousViewData .MaliciousSite (url, status.feed, false )
217
238
}
218
239
is Safe -> {
219
- processedUrls.add( decodedUrl)
240
+ processedUrls[ decodedUrl] = Safe
220
241
return @runBlocking IsMaliciousViewData .Safe
221
242
}
222
243
}
223
244
}
224
245
is WaitForConfirmation -> {
225
- processedUrls.add(decodedUrl)
226
246
return @runBlocking IsMaliciousViewData .WaitForConfirmation
227
247
}
228
248
}
@@ -231,6 +251,10 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
231
251
}
232
252
}
233
253
254
+ private fun firePixelForMaliciousIframe (feed : Feed ) {
255
+ pixel.fire(AppPixelName .MALICIOUS_SITE_DETECTED_IN_IFRAME , mapOf (" category" to feed.name.lowercase()))
256
+ }
257
+
234
258
private suspend fun checkMaliciousUrl (
235
259
url : String ,
236
260
confirmationCallback : (maliciousStatus: MaliciousStatus ) -> Unit ,
@@ -243,7 +267,7 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
243
267
} else {
244
268
Safe
245
269
}
246
- processedUrls.clear()
270
+ processedUrls[url] = it
247
271
confirmationCallback(isMalicious)
248
272
}
249
273
}
@@ -257,6 +281,17 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
257
281
return isFeatureEnabled && isSettingEnabled
258
282
}
259
283
284
+ override fun onPageLoadStarted (url : String ) {
285
+ val convertedUrl = URLDecoder .decode(url, " UTF-8" ).lowercase()
286
+ /* onPageLoadStarted is often called after shouldOverride/shouldIntercept, therefore, if the URL
287
+ * is already stored, we don't clear the processedUrls map to avoid re-checking the URL for the same
288
+ * page load.
289
+ */
290
+ if (! processedUrls.contains(convertedUrl)) {
291
+ processedUrls.clear()
292
+ }
293
+ }
294
+
260
295
private fun decodeUrl (url : Uri ): String {
261
296
return try {
262
297
URLDecoder .decode(url.toString(), " UTF-8" ).lowercase()
@@ -266,18 +301,12 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
266
301
}
267
302
}
268
303
269
- override fun onPageLoadStarted () {
270
- processedUrls.clear()
271
- }
272
-
273
304
override fun onSiteExempted (
274
305
url : Uri ,
275
306
feed : Feed ,
276
307
) {
277
308
val convertedUrl = decodeUrl(url)
278
309
exemptedUrlsHolder.addExemptedMaliciousUrl(ExemptedUrl (convertedUrl.toUri(), feed))
279
- Timber .tag(" MaliciousSiteDetector" ).d(
280
- " Added $url to exemptedUrls, contents: ${exemptedUrlsHolder.exemptedMaliciousUrls} " ,
281
- )
310
+ Timber .d(" Added $url to exemptedUrls" )
282
311
}
283
312
}
0 commit comments