1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/subresource_filter/content/browser/subframe_navigation_filtering_throttle.h"
6 
7 #include <memory>
8 #include <sstream>
9 #include <string>
10 
11 #include "base/bind_helpers.h"
12 #include "base/callback.h"
13 #include "base/run_loop.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/test/metrics/histogram_tester.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "components/subresource_filter/content/browser/async_document_subresource_filter.h"
18 #include "components/subresource_filter/content/browser/async_document_subresource_filter_test_utils.h"
19 #include "components/subresource_filter/content/browser/subframe_navigation_test_utils.h"
20 #include "components/subresource_filter/content/browser/subresource_filter_observer_test_utils.h"
21 #include "components/subresource_filter/core/browser/subresource_filter_constants.h"
22 #include "components/subresource_filter/core/common/test_ruleset_creator.h"
23 #include "components/subresource_filter/core/mojom/subresource_filter.mojom.h"
24 #include "content/public/browser/navigation_handle.h"
25 #include "content/public/browser/web_contents_observer.h"
26 #include "content/public/test/navigation_simulator.h"
27 #include "content/public/test/test_renderer_host.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29 #include "url/gurl.h"
30 #include "url/origin.h"
31 
32 namespace subresource_filter {
33 
34 class MockDelegate : public SubframeNavigationFilteringThrottle::Delegate {
35  public:
36   MockDelegate() = default;
37   ~MockDelegate() override = default;
38 
39   // SubframeNavigationFilteringThrottle::Delegate:
CalculateIsAdSubframe(content::RenderFrameHost * frame_host,LoadPolicy load_policy)40   bool CalculateIsAdSubframe(content::RenderFrameHost* frame_host,
41                              LoadPolicy load_policy) override {
42     return false;
43   }
44 
45   DISALLOW_COPY_AND_ASSIGN(MockDelegate);
46 };
47 
48 class SubframeNavigationFilteringThrottleTest
49     : public content::RenderViewHostTestHarness,
50       public content::WebContentsObserver {
51  public:
SubframeNavigationFilteringThrottleTest()52   SubframeNavigationFilteringThrottleTest() {}
~SubframeNavigationFilteringThrottleTest()53   ~SubframeNavigationFilteringThrottleTest() override {}
54 
SetUp()55   void SetUp() override {
56     content::RenderViewHostTestHarness::SetUp();
57     NavigateAndCommit(GURL("https://example.test"));
58     Observe(RenderViewHostTestHarness::web_contents());
59   }
60 
TearDown()61   void TearDown() override {
62     dealer_handle_.reset();
63     ruleset_handle_.reset();
64     parent_filter_.reset();
65     RunUntilIdle();
66     content::RenderViewHostTestHarness::TearDown();
67   }
68 
navigation_simulator()69   content::NavigationSimulator* navigation_simulator() {
70     return navigation_simulator_.get();
71   }
72 
73   // content::WebContentsObserver:
DidStartNavigation(content::NavigationHandle * navigation_handle)74   void DidStartNavigation(
75       content::NavigationHandle* navigation_handle) override {
76     ASSERT_FALSE(navigation_handle->IsInMainFrame());
77     // The |parent_filter_| is the parent frame's filter. Do not register a
78     // throttle if the parent is not activated with a valid filter.
79     if (parent_filter_) {
80       auto throttle = std::make_unique<SubframeNavigationFilteringThrottle>(
81           navigation_handle, parent_filter_.get(), &mock_delegate_);
82       ASSERT_NE(nullptr, throttle->GetNameForLogging());
83       navigation_handle->RegisterThrottleForTesting(std::move(throttle));
84     }
85   }
86 
InitializeDocumentSubresourceFilter(const GURL & document_url,mojom::ActivationLevel parent_level=mojom::ActivationLevel::kEnabled)87   void InitializeDocumentSubresourceFilter(
88       const GURL& document_url,
89       mojom::ActivationLevel parent_level = mojom::ActivationLevel::kEnabled) {
90     ASSERT_NO_FATAL_FAILURE(
91         test_ruleset_creator_.CreateRulesetToDisallowURLsWithPathSuffix(
92             "disallowed.html", &test_ruleset_pair_));
93 
94     // Make the blocking task runner run on the current task runner for the
95     // tests, to ensure that the NavigationSimulator properly runs all necessary
96     // tasks while waiting for throttle checks to finish.
97     dealer_handle_ = std::make_unique<VerifiedRulesetDealer::Handle>(
98         base::ThreadTaskRunnerHandle::Get());
99     dealer_handle_->TryOpenAndSetRulesetFile(test_ruleset_pair_.indexed.path,
100                                              /*expected_checksum=*/0,
101                                              base::DoNothing());
102     ruleset_handle_ =
103         std::make_unique<VerifiedRuleset::Handle>(dealer_handle_.get());
104 
105     testing::TestActivationStateCallbackReceiver activation_state;
106     mojom::ActivationState parent_activation_state;
107     parent_activation_state.activation_level = parent_level;
108     parent_activation_state.enable_logging = true;
109     parent_filter_ = std::make_unique<AsyncDocumentSubresourceFilter>(
110         ruleset_handle_.get(),
111         AsyncDocumentSubresourceFilter::InitializationParams(
112             document_url, url::Origin::Create(document_url),
113             parent_activation_state),
114         activation_state.GetCallback());
115     RunUntilIdle();
116     activation_state.ExpectReceivedOnce(parent_activation_state);
117   }
118 
RunUntilIdle()119   void RunUntilIdle() { base::RunLoop().RunUntilIdle(); }
120 
CreateTestSubframeAndInitNavigation(const GURL & first_url,content::RenderFrameHost * parent)121   void CreateTestSubframeAndInitNavigation(const GURL& first_url,
122                                            content::RenderFrameHost* parent) {
123     content::RenderFrameHost* render_frame =
124         content::RenderFrameHostTester::For(parent)->AppendChild(
125             base::StringPrintf("subframe-%s", first_url.spec().c_str()));
126     navigation_simulator_ =
127         content::NavigationSimulator::CreateRendererInitiated(first_url,
128                                                               render_frame);
129   }
130 
GetConsoleMessages()131   const std::vector<std::string>& GetConsoleMessages() {
132     return content::RenderFrameHostTester::For(main_rfh())
133         ->GetConsoleMessages();
134   }
135 
GetFilterConsoleMessage(const GURL & filtered_url)136   std::string GetFilterConsoleMessage(const GURL& filtered_url) {
137     return base::StringPrintf(kDisallowSubframeConsoleMessageFormat,
138                               filtered_url.possibly_invalid_spec().c_str());
139   }
140 
141  private:
142   testing::TestRulesetCreator test_ruleset_creator_;
143   testing::TestRulesetPair test_ruleset_pair_;
144 
145   MockDelegate mock_delegate_;
146   std::unique_ptr<VerifiedRulesetDealer::Handle> dealer_handle_;
147   std::unique_ptr<VerifiedRuleset::Handle> ruleset_handle_;
148 
149   std::unique_ptr<AsyncDocumentSubresourceFilter> parent_filter_;
150 
151   std::unique_ptr<content::NavigationSimulator> navigation_simulator_;
152 
153   DISALLOW_COPY_AND_ASSIGN(SubframeNavigationFilteringThrottleTest);
154 };
155 
TEST_F(SubframeNavigationFilteringThrottleTest,FilterOnStart)156 TEST_F(SubframeNavigationFilteringThrottleTest, FilterOnStart) {
157   InitializeDocumentSubresourceFilter(GURL("https://example.test"));
158   const GURL url("https://example.test/disallowed.html");
159   CreateTestSubframeAndInitNavigation(url, main_rfh());
160   EXPECT_EQ(content::NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE,
161             SimulateStartAndGetResult(navigation_simulator()));
162   EXPECT_TRUE(
163       base::Contains(GetConsoleMessages(), GetFilterConsoleMessage(url)));
164 }
165 
TEST_F(SubframeNavigationFilteringThrottleTest,FilterOnRedirect)166 TEST_F(SubframeNavigationFilteringThrottleTest, FilterOnRedirect) {
167   InitializeDocumentSubresourceFilter(GURL("https://example.test"));
168   CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
169                                       main_rfh());
170 
171   EXPECT_EQ(content::NavigationThrottle::PROCEED,
172             SimulateStartAndGetResult(navigation_simulator()));
173   EXPECT_EQ(content::NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE,
174             SimulateRedirectAndGetResult(
175                 navigation_simulator(),
176                 GURL("https://example.test/disallowed.html")));
177 }
178 
TEST_F(SubframeNavigationFilteringThrottleTest,DryRunOnStart)179 TEST_F(SubframeNavigationFilteringThrottleTest, DryRunOnStart) {
180   InitializeDocumentSubresourceFilter(GURL("https://example.test"),
181                                       mojom::ActivationLevel::kDryRun);
182   const GURL url("https://example.test/disallowed.html");
183   CreateTestSubframeAndInitNavigation(url, main_rfh());
184 
185   EXPECT_EQ(content::NavigationThrottle::PROCEED,
186             SimulateStartAndGetResult(navigation_simulator()));
187   EXPECT_FALSE(
188       base::Contains(GetConsoleMessages(), GetFilterConsoleMessage(url)));
189 }
190 
TEST_F(SubframeNavigationFilteringThrottleTest,DryRunOnRedirect)191 TEST_F(SubframeNavigationFilteringThrottleTest, DryRunOnRedirect) {
192   InitializeDocumentSubresourceFilter(GURL("https://example.test"),
193                                       mojom::ActivationLevel::kDryRun);
194   CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
195                                       main_rfh());
196 
197   EXPECT_EQ(content::NavigationThrottle::PROCEED,
198             SimulateStartAndGetResult(navigation_simulator()));
199   EXPECT_EQ(content::NavigationThrottle::PROCEED,
200             SimulateRedirectAndGetResult(
201                 navigation_simulator(),
202                 GURL("https://example.test/disallowed.html")));
203 }
204 
TEST_F(SubframeNavigationFilteringThrottleTest,FilterOnSecondRedirect)205 TEST_F(SubframeNavigationFilteringThrottleTest, FilterOnSecondRedirect) {
206   InitializeDocumentSubresourceFilter(GURL("https://example.test"));
207   CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
208                                       main_rfh());
209 
210   EXPECT_EQ(content::NavigationThrottle::PROCEED,
211             SimulateStartAndGetResult(navigation_simulator()));
212   EXPECT_EQ(
213       content::NavigationThrottle::PROCEED,
214       SimulateRedirectAndGetResult(navigation_simulator(),
215                                    GURL("https://example.test/allowed2.html")));
216   EXPECT_EQ(content::NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE,
217             SimulateRedirectAndGetResult(
218                 navigation_simulator(),
219                 GURL("https://example.test/disallowed.html")));
220 }
221 
TEST_F(SubframeNavigationFilteringThrottleTest,NeverFilterNonMatchingRule)222 TEST_F(SubframeNavigationFilteringThrottleTest, NeverFilterNonMatchingRule) {
223   InitializeDocumentSubresourceFilter(GURL("https://example.test"));
224   CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
225                                       main_rfh());
226 
227   EXPECT_EQ(content::NavigationThrottle::PROCEED,
228             SimulateStartAndGetResult(navigation_simulator()));
229   EXPECT_EQ(
230       content::NavigationThrottle::PROCEED,
231       SimulateRedirectAndGetResult(navigation_simulator(),
232                                    GURL("https://example.test/allowed2.html")));
233   EXPECT_EQ(content::NavigationThrottle::PROCEED,
234             SimulateCommitAndGetResult(navigation_simulator()));
235 }
236 
TEST_F(SubframeNavigationFilteringThrottleTest,FilterSubsubframe)237 TEST_F(SubframeNavigationFilteringThrottleTest, FilterSubsubframe) {
238   // Fake an activation of the subframe.
239   content::RenderFrameHost* parent_subframe =
240       content::RenderFrameHostTester::For(main_rfh())
241           ->AppendChild("parent-sub");
242   GURL test_url = GURL("https://example.test");
243   auto navigation = content::NavigationSimulator::CreateRendererInitiated(
244       test_url, parent_subframe);
245   navigation->Start();
246   InitializeDocumentSubresourceFilter(GURL("https://example.test"));
247   navigation->Commit();
248 
249   CreateTestSubframeAndInitNavigation(
250       GURL("https://example.test/disallowed.html"), parent_subframe);
251   EXPECT_EQ(content::NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE,
252             SimulateStartAndGetResult(navigation_simulator()));
253 }
254 
TEST_F(SubframeNavigationFilteringThrottleTest,DelayMetrics)255 TEST_F(SubframeNavigationFilteringThrottleTest, DelayMetrics) {
256   base::HistogramTester histogram_tester;
257   InitializeDocumentSubresourceFilter(GURL("https://example.test"));
258   CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
259                                       main_rfh());
260   navigation_simulator()->SetTransition(ui::PAGE_TRANSITION_MANUAL_SUBFRAME);
261   EXPECT_EQ(content::NavigationThrottle::PROCEED,
262             SimulateStartAndGetResult(navigation_simulator()));
263   EXPECT_EQ(content::NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE,
264             SimulateRedirectAndGetResult(
265                 navigation_simulator(),
266                 GURL("https://example.test/disallowed.html")));
267 
268   navigation_simulator()->CommitErrorPage();
269 
270   const char kFilterDelayDisallowed[] =
271       "SubresourceFilter.DocumentLoad.SubframeFilteringDelay.Disallowed2";
272   const char kFilterDelayWouldDisallow[] =
273       "SubresourceFilter.DocumentLoad.SubframeFilteringDelay.WouldDisallow";
274   const char kFilterDelayAllowed[] =
275       "SubresourceFilter.DocumentLoad.SubframeFilteringDelay.Allowed";
276   histogram_tester.ExpectTotalCount(kFilterDelayDisallowed, 1);
277   histogram_tester.ExpectTotalCount(kFilterDelayWouldDisallow, 0);
278   histogram_tester.ExpectTotalCount(kFilterDelayAllowed, 0);
279 
280   CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
281                                       main_rfh());
282   EXPECT_EQ(content::NavigationThrottle::PROCEED,
283             SimulateStartAndGetResult(navigation_simulator()));
284   EXPECT_EQ(content::NavigationThrottle::PROCEED,
285             SimulateCommitAndGetResult(navigation_simulator()));
286 
287   histogram_tester.ExpectTotalCount(kFilterDelayDisallowed, 1);
288   histogram_tester.ExpectTotalCount(kFilterDelayWouldDisallow, 0);
289   histogram_tester.ExpectTotalCount(kFilterDelayAllowed, 1);
290 }
291 
TEST_F(SubframeNavigationFilteringThrottleTest,DelayMetricsDryRun)292 TEST_F(SubframeNavigationFilteringThrottleTest, DelayMetricsDryRun) {
293   base::HistogramTester histogram_tester;
294   InitializeDocumentSubresourceFilter(GURL("https://example.test"),
295                                       mojom::ActivationLevel::kDryRun);
296   CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
297                                       main_rfh());
298   navigation_simulator()->SetTransition(ui::PAGE_TRANSITION_MANUAL_SUBFRAME);
299   EXPECT_EQ(content::NavigationThrottle::PROCEED,
300             SimulateStartAndGetResult(navigation_simulator()));
301   EXPECT_EQ(content::NavigationThrottle::PROCEED,
302             SimulateRedirectAndGetResult(
303                 navigation_simulator(),
304                 GURL("https://example.test/disallowed.html")));
305   navigation_simulator()->Commit();
306 
307   const char kFilterDelayDisallowed[] =
308       "SubresourceFilter.DocumentLoad.SubframeFilteringDelay.Disallowed2";
309   const char kFilterDelayWouldDisallow[] =
310       "SubresourceFilter.DocumentLoad.SubframeFilteringDelay.WouldDisallow";
311   const char kFilterDelayAllowed[] =
312       "SubresourceFilter.DocumentLoad.SubframeFilteringDelay.Allowed";
313   histogram_tester.ExpectTotalCount(kFilterDelayDisallowed, 0);
314   histogram_tester.ExpectTotalCount(kFilterDelayWouldDisallow, 1);
315   histogram_tester.ExpectTotalCount(kFilterDelayAllowed, 0);
316 
317   CreateTestSubframeAndInitNavigation(GURL("https://example.test/allowed.html"),
318                                       main_rfh());
319   EXPECT_EQ(content::NavigationThrottle::PROCEED,
320             SimulateStartAndGetResult(navigation_simulator()));
321   EXPECT_EQ(content::NavigationThrottle::PROCEED,
322             SimulateCommitAndGetResult(navigation_simulator()));
323 
324   histogram_tester.ExpectTotalCount(kFilterDelayDisallowed, 0);
325   histogram_tester.ExpectTotalCount(kFilterDelayWouldDisallow, 1);
326   histogram_tester.ExpectTotalCount(kFilterDelayAllowed, 1);
327 }
328 
329 }  // namespace subresource_filter
330