Skip to content

Commit 9e846ca

Browse files
authored
Do not block first party sites
1 parent d2b51f8 commit 9e846ca

File tree

15 files changed

+326
-163
lines changed

15 files changed

+326
-163
lines changed

app/src/androidTest/java/com/duckduckgo/app/browser/DuckDuckGoUrlDetectorTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class DuckDuckGoUrlDetectorTest {
2626
private lateinit var testee: DuckDuckGoUrlDetector
2727

2828
@Before
29-
fun setup(){
29+
fun setup() {
3030
testee = DuckDuckGoUrlDetector()
3131
}
3232

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2017 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.global
18+
19+
import com.duckduckgo.app.global.UriString.Companion.sameOrSubdomain
20+
import org.junit.Assert.assertFalse
21+
import org.junit.Assert.assertTrue
22+
import org.junit.Test
23+
24+
class UriStringTest {
25+
26+
@Test
27+
fun whenUrlsHaveSameDomainThenSameOrSubdomainIsTrue() {
28+
assertTrue(sameOrSubdomain("http://example.com/index.html", "http://example.com/home.html"))
29+
}
30+
31+
@Test
32+
fun whenUrlIsSubdomainThenSameOrSubdomainIsTrue() {
33+
assertTrue(sameOrSubdomain("http://subdomain.example.com/index.html", "http://example.com/home.html"))
34+
}
35+
36+
@Test
37+
fun whenUrlIsAParentDomainThenSameOrSubdomainIsFalse() {
38+
assertFalse(sameOrSubdomain("http://example.com/index.html", "http://parent.example.com/home.html"))
39+
}
40+
41+
@Test
42+
fun whenChildUrlIsMalformedThenSameOrSubdomainIsFalse() {
43+
assertFalse(sameOrSubdomain("??.example.com/index.html", "http://example.com/home.html"))
44+
}
45+
46+
@Test
47+
fun whenParentUrlIsMalformedThenSameOrSubdomainIsFalse() {
48+
assertFalse(sameOrSubdomain("http://example.com/index.html", "??.example.com/home.html"))
49+
}
50+
51+
}

app/src/androidTest/java/com/duckduckgo/app/trackerdetection/TrackerDetectorInstrumentationTest.kt

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,62 +16,107 @@
1616

1717
package com.duckduckgo.app.trackerdetection
1818

19-
20-
import android.support.test.runner.AndroidJUnit4
21-
import com.duckduckgo.app.trackerdetection.Client.ClientName.EASYLIST
22-
import com.duckduckgo.app.trackerdetection.Client.ClientName.EASYPRIVACY
19+
import com.duckduckgo.app.trackerdetection.model.DisconnectTracker
20+
import com.duckduckgo.app.trackerdetection.model.NetworkTrackers
2321
import com.duckduckgo.app.trackerdetection.model.ResourceType
22+
import com.nhaarman.mockito_kotlin.any
23+
import com.nhaarman.mockito_kotlin.mock
24+
import com.nhaarman.mockito_kotlin.whenever
2425
import org.junit.Assert.assertFalse
2526
import org.junit.Assert.assertTrue
26-
import org.junit.Before
2727
import org.junit.Test
28-
import org.junit.runner.RunWith
28+
import org.mockito.ArgumentMatchers.anyString
29+
import java.util.*
2930

3031

31-
@RunWith(AndroidJUnit4::class)
3232
class TrackerDetectorInstrumentationTest {
3333

34+
private val networkTrackers = NetworkTrackers()
35+
private val trackerDetector = TrackerDetector(networkTrackers)
36+
3437
companion object {
35-
private val documentUrl = "http://example.com"
3638
private val resourceType = ResourceType.UNKNOWN
39+
private val network = "Network"
40+
}
41+
42+
@Test
43+
fun whenThereAreNoClientsThenShouldBlockIsFalse() {
44+
assertFalse(trackerDetector.shouldBlock("http://thirdparty.com/update.js", "http://example.com/index.com", resourceType))
45+
}
46+
47+
@Test
48+
fun whenAllClientsFailToMatchThenShouldBlockIsFalse() {
49+
trackerDetector.addClient(neverMatchingClient())
50+
trackerDetector.addClient(neverMatchingClient())
51+
assertFalse(trackerDetector.shouldBlock("http://thirdparty.com/update.js", "http://example.com/index.com", resourceType))
52+
}
53+
54+
@Test
55+
fun whenAllClientsMatchThenShouldBlockIsTrue() {
56+
trackerDetector.addClient(alwaysMatchingClient())
57+
trackerDetector.addClient(alwaysMatchingClient())
58+
assertTrue(trackerDetector.shouldBlock("http://thirdparty.com/update.js", "http://example.com/index.com", resourceType))
3759
}
3860

39-
private lateinit var testee: TrackerDetector
61+
@Test
62+
fun whenSomeClientsMatchThenShouldBlockIsTrue() {
63+
trackerDetector.addClient(neverMatchingClient())
64+
trackerDetector.addClient(alwaysMatchingClient())
65+
assertTrue(trackerDetector.shouldBlock("http://thirdparty.com/update.js", "http://example.com/index.com", resourceType))
66+
}
67+
68+
@Test
69+
fun whenUrlHasSameDomainAsDocumentThenShouldBlockIsFalse() {
70+
trackerDetector.addClient(alwaysMatchingClient())
71+
assertFalse(trackerDetector.shouldBlock("http://example.com/update.js", "http://example.com/index.com", resourceType))
72+
}
4073

41-
@Before
42-
fun before() {
43-
val easylistAdblock = adblockClient(EASYLIST, "binary/easylist_sample")
44-
val easyprivacyAdblock = adblockClient(EASYPRIVACY, "binary/easyprivacy_sample")
45-
testee = TrackerDetector()
46-
testee.addClient(easyprivacyAdblock)
47-
testee.addClient(easylistAdblock)
74+
@Test
75+
fun whenUrlIsSubdomainOfDocumentThenShouldBlockIsFalse() {
76+
trackerDetector.addClient(alwaysMatchingClient())
77+
assertFalse(trackerDetector.shouldBlock("http://mobile.example.com/update.js", "http://example.com/index.com", resourceType))
4878
}
4979

5080
@Test
51-
fun whenUrlIsInEasyListThenShouldBlockIsTrue() {
52-
val url = "http://imasdk.googleapis.com/js/sdkloader/ima3.js"
53-
assertTrue(testee.shouldBlock(url, documentUrl, resourceType))
81+
fun whenUrlIsParentOfDocumentThenShouldBlockIsFalse() {
82+
trackerDetector.addClient(alwaysMatchingClient())
83+
assertFalse(trackerDetector.shouldBlock("http://example.com/update.js", "http://mobile.example.com/index.com", resourceType))
5484
}
5585

5686
@Test
57-
fun whenUrlIsInEasyPrivacyListThenShouldBlockIsTrue() {
58-
val url = "http://cdn.tagcommander.com/1705/tc_catalog.css"
59-
assertTrue(testee.shouldBlock(url, documentUrl, resourceType))
87+
fun whenUrlIsNetworkOfDocumentThenShouldBlockIsFalse() {
88+
val networks = Arrays.asList(DisconnectTracker("example.com", "", network, "http://thirdparty.com/"))
89+
networkTrackers.updateData(networks)
90+
assertFalse(trackerDetector.shouldBlock("http://thirdparty.com/update.js", "http://example.com/index.com", resourceType))
6091
}
6192

6293
@Test
63-
fun whenUrlIsNotInAnyTrackerListsThenShouldBlockIsFalse() {
64-
val url = "https://duckduckgo.com/index.html"
65-
assertFalse(testee.shouldBlock(url, documentUrl, resourceType))
94+
fun whenDocumentIsNetworkOfUrlThenShouldBlockIsFalse() {
95+
val networks = Arrays.asList(DisconnectTracker("thirdparty.com", "", network, "http://example.com"))
96+
networkTrackers.updateData(networks)
97+
assertFalse(trackerDetector.shouldBlock("http://thirdparty.com/update.js", "http://example.com/index.com", resourceType))
98+
}
99+
100+
@Test
101+
fun whenUrlSharesSameNetworkAsDocumentThenShouldBlockIsFalse() {
102+
val networks = Arrays.asList(
103+
DisconnectTracker("thirdparty.com", "", network, "http://network.com"),
104+
DisconnectTracker("example.com", "", network, "http://network.com")
105+
)
106+
networkTrackers.updateData(networks)
107+
assertFalse(trackerDetector.shouldBlock("http://thirdparty.com/update.js", "http://example.com/index.com", resourceType))
108+
}
109+
110+
private fun alwaysMatchingClient(): Client {
111+
val client: Client = mock()
112+
whenever(client.matches(anyString(), anyString(), any())).thenReturn(true)
113+
return client
66114
}
67115

68-
private fun adblockClient(name: Client.ClientName, dataFile: String): Client {
69-
val data = javaClass.classLoader.getResource(dataFile).readBytes()
70-
val initialAdBlock = AdBlockClient(name)
71-
initialAdBlock.loadBasicData(data)
72-
val adblockWithProcessedData = AdBlockClient(name)
73-
adblockWithProcessedData.loadProcessedData(initialAdBlock.getProcessedData())
74-
return adblockWithProcessedData
116+
private fun neverMatchingClient(): Client {
117+
val client: Client = mock()
118+
whenever(client.matches(anyString(), anyString(), any())).thenReturn(false)
119+
return client
75120
}
76121

77122
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (c) 2017 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.trackerdetection
18+
19+
20+
import android.support.test.runner.AndroidJUnit4
21+
import com.duckduckgo.app.trackerdetection.Client.ClientName.EASYLIST
22+
import com.duckduckgo.app.trackerdetection.Client.ClientName.EASYPRIVACY
23+
import com.duckduckgo.app.trackerdetection.model.NetworkTrackers
24+
import com.duckduckgo.app.trackerdetection.model.ResourceType
25+
import org.junit.Assert.assertFalse
26+
import org.junit.Assert.assertTrue
27+
import org.junit.Before
28+
import org.junit.Test
29+
import org.junit.runner.RunWith
30+
31+
32+
@RunWith(AndroidJUnit4::class)
33+
class TrackerDetectorListInstrumentationTest {
34+
35+
companion object {
36+
private val documentUrl = "http://example.com"
37+
private val resourceType = ResourceType.UNKNOWN
38+
}
39+
40+
private lateinit var testee: TrackerDetector
41+
42+
@Before
43+
fun before() {
44+
val easylistAdblock = adblockClient(EASYLIST, "binary/easylist_sample")
45+
val easyprivacyAdblock = adblockClient(EASYPRIVACY, "binary/easyprivacy_sample")
46+
testee = TrackerDetector(NetworkTrackers())
47+
testee.addClient(easyprivacyAdblock)
48+
testee.addClient(easylistAdblock)
49+
}
50+
51+
@Test
52+
fun whenUrlIsInEasyListThenShouldBlockIsTrue() {
53+
val url = "http://imasdk.googleapis.com/js/sdkloader/ima3.js"
54+
assertTrue(testee.shouldBlock(url, documentUrl, resourceType))
55+
}
56+
57+
@Test
58+
fun whenUrlIsInEasyPrivacyListThenShouldBlockIsTrue() {
59+
val url = "http://cdn.tagcommander.com/1705/tc_catalog.css"
60+
assertTrue(testee.shouldBlock(url, documentUrl, resourceType))
61+
}
62+
63+
@Test
64+
fun whenUrlIsNotInAnyTrackerListsThenShouldBlockIsFalse() {
65+
val url = "https://duckduckgo.com/index.html"
66+
assertFalse(testee.shouldBlock(url, documentUrl, resourceType))
67+
}
68+
69+
private fun adblockClient(name: Client.ClientName, dataFile: String): Client {
70+
val data = javaClass.classLoader.getResource(dataFile).readBytes()
71+
val initialAdBlock = AdBlockClient(name)
72+
initialAdBlock.loadBasicData(data)
73+
val adblockWithProcessedData = AdBlockClient(name)
74+
adblockWithProcessedData.loadProcessedData(initialAdBlock.getProcessedData())
75+
return adblockWithProcessedData
76+
}
77+
78+
}

app/src/main/java/com/duckduckgo/app/browser/BrowserWebViewClient.kt

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import com.duckduckgo.app.trackerdetection.model.ResourceType
2828
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
2929
import io.reactivex.Observable
3030
import io.reactivex.android.schedulers.AndroidSchedulers
31-
import kotlinx.android.synthetic.main.activity_browser.view.*
3231
import timber.log.Timber
3332
import javax.inject.Inject
3433

@@ -39,68 +38,68 @@ class BrowserWebViewClient @Inject constructor(
3938
) : WebViewClient() {
4039

4140
var webViewClientListener: WebViewClientListener? = null
41+
private var currentUrl: String? = null
4242

4343

4444
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
45-
Timber.v("Url ${request.url}")
46-
4745
if (requestRewriter.shouldRewriteRequest(request)) {
4846
val newUri = requestRewriter.rewriteRequestWithCustomQueryParams(request.url)
4947
view.loadUrl(newUri.toString())
5048
return true
5149
}
52-
53-
if (block(request, view.url)) {
54-
return true
55-
}
56-
5750
return false
5851
}
5952

6053
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
54+
currentUrl = url
6155
webViewClientListener?.loadingStarted()
6256
webViewClientListener?.urlChanged(url)
6357
}
6458

6559
override fun onPageFinished(view: WebView?, url: String?) {
6660
webViewClientListener?.loadingFinished()
67-
webViewClientListener?.urlChanged(url)
6861
}
6962

7063
@WorkerThread
7164
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
7265
Timber.v("Intercepting resource ${request.url}")
7366

74-
if (block(request, view.safeUrl())) {
67+
if (view.elementClicked() == request.url.toString()) {
68+
return null
69+
}
70+
71+
if (block(request, currentUrl)) {
7572
return WebResourceResponse(null, null, null)
7673
}
7774

7875
return null
7976
}
8077

8178
private fun block(request: WebResourceRequest, documentUrl: String?): Boolean {
82-
8379
val url = request.url.toString()
80+
8481
if (documentUrl != null && trackerDetector.shouldBlock(url, documentUrl, ResourceType.from(request))) {
85-
Timber.v("WAS BLOCKED $url")
8682
webViewClientListener?.trackerDetected(TrackingEvent(url, documentUrl, true))
8783
return true
8884
}
89-
90-
Timber.v("NOT blocked $url")
85+
9186
return false
9287
}
9388

89+
private fun WebView.elementClicked(): String? {
90+
return safeHitTestResult()?.extra
91+
}
9492

9593
/**
96-
* Access the webview url from any thread; jumps onto the main thread to achieve this
94+
* Access the webview hit test result from any thread; jumps onto the main thread to achieve this
9795
*/
9896
@AnyThread
99-
private fun WebView.safeUrl(): String? {
100-
return Observable.just(webView)
97+
private fun WebView.safeHitTestResult(): WebView.HitTestResult {
98+
return Observable.just(this)
10199
.observeOn(AndroidSchedulers.mainThread())
102-
.map { webView -> webView.url }
100+
.map { webView ->
101+
webView.hitTestResult
102+
}
103103
.blockingFirst()
104104
}
105-
106105
}

app/src/main/java/com/duckduckgo/app/di/NetworkModule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ package com.duckduckgo.app.di
1818

1919
import android.content.Context
2020
import com.duckduckgo.app.browser.R
21-
import com.duckduckgo.app.trackerdetection.api.TrackerListService
2221
import com.duckduckgo.app.trackerdetection.api.DisconnectJsonAdapter
22+
import com.duckduckgo.app.trackerdetection.api.TrackerListService
2323
import com.squareup.moshi.Moshi
2424
import dagger.Module
2525
import dagger.Provides

0 commit comments

Comments
 (0)