1 // Copyright 2020 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 <stdint.h>
6 #include <memory>
7 
8 #include "base/bind.h"
9 #include "base/test/scoped_feature_list.h"
10 #include "content/browser/conversions/conversion_host.h"
11 #include "content/browser/conversions/conversion_manager_impl.h"
12 #include "content/browser/web_contents/web_contents_impl.h"
13 #include "content/public/common/content_features.h"
14 #include "content/public/common/content_switches.h"
15 #include "content/public/test/browser_test.h"
16 #include "content/public/test/browser_test_utils.h"
17 #include "content/public/test/content_browser_test.h"
18 #include "content/public/test/content_browser_test_utils.h"
19 #include "content/shell/browser/shell.h"
20 #include "content/test/resource_load_observer.h"
21 #include "net/dns/mock_host_resolver.h"
22 #include "net/test/embedded_test_server/default_handlers.h"
23 #include "net/test/embedded_test_server/embedded_test_server.h"
24 #include "third_party/blink/public/mojom/conversions/conversions.mojom.h"
25 #include "url/gurl.h"
26 
27 namespace content {
28 
29 namespace {
30 
31 // Well known path for registering conversions.
32 const std::string kWellKnownUrl = ".well-known/register-conversion";
33 
34 }  // namespace
35 
36 // A mock conversion host which waits for a conversion registration
37 // mojo message is received. Tracks the last seen conversion data.
38 class TestConversionHost : public ConversionHost {
39  public:
ReplaceAndGetConversionHost(WebContents * contents)40   static std::unique_ptr<TestConversionHost> ReplaceAndGetConversionHost(
41       WebContents* contents) {
42     static_cast<WebContentsImpl*>(contents)->RemoveReceiverSetForTesting(
43         blink::mojom::ConversionHost::Name_);
44     return std::make_unique<TestConversionHost>(contents);
45   }
46 
TestConversionHost(WebContents * contents)47   explicit TestConversionHost(WebContents* contents)
48       : ConversionHost(contents) {}
49 
RegisterConversion(blink::mojom::ConversionPtr conversion)50   void RegisterConversion(blink::mojom::ConversionPtr conversion) override {
51     last_conversion_data_ = conversion->conversion_data;
52     num_conversions_++;
53 
54     // Don't quit the run loop if we have not seen the expected number of
55     // conversions.
56     if (num_conversions_ < expected_num_conversions_)
57       return;
58     conversion_waiter_.Quit();
59   }
60 
61   // Returns the last conversion data after |expected_num_conversions| have been
62   // observed.
WaitForNumConversions(size_t expected_num_conversions)63   uint64_t WaitForNumConversions(size_t expected_num_conversions) {
64     if (expected_num_conversions == num_conversions_)
65       return last_conversion_data_;
66     expected_num_conversions_ = expected_num_conversions;
67     conversion_waiter_.Run();
68     return last_conversion_data_;
69   }
70 
num_conversions()71   size_t num_conversions() { return num_conversions_; }
72 
73  private:
74   uint64_t last_conversion_data_ = 0;
75   size_t num_conversions_ = 0;
76   size_t expected_num_conversions_ = 0;
77   base::RunLoop conversion_waiter_;
78 };
79 
80 class ConversionDisabledBrowserTest : public ContentBrowserTest {
81  public:
ConversionDisabledBrowserTest()82   ConversionDisabledBrowserTest() {
83     ConversionManagerImpl::RunInMemoryForTesting();
84     feature_list_.InitAndEnableFeature(features::kConversionMeasurement);
85   }
86 
SetUpOnMainThread()87   void SetUpOnMainThread() override {
88     host_resolver()->AddRule("*", "127.0.0.1");
89     embedded_test_server()->ServeFilesFromSourceDirectory(
90         "content/test/data/conversions");
91     SetupCrossSiteRedirector(embedded_test_server());
92     ASSERT_TRUE(embedded_test_server()->Start());
93 
94     https_server_ = std::make_unique<net::EmbeddedTestServer>(
95         net::EmbeddedTestServer::TYPE_HTTPS);
96     https_server_->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
97     net::test_server::RegisterDefaultHandlers(https_server_.get());
98     https_server_->ServeFilesFromSourceDirectory(
99         "content/test/data/conversions");
100     SetupCrossSiteRedirector(https_server_.get());
101     ASSERT_TRUE(https_server_->Start());
102   }
103 
web_contents()104   WebContents* web_contents() { return shell()->web_contents(); }
105 
https_server()106   net::EmbeddedTestServer* https_server() { return https_server_.get(); }
107 
108  protected:
109   base::test::ScopedFeatureList feature_list_;
110 
111  private:
112   std::unique_ptr<net::EmbeddedTestServer> https_server_;
113 };
114 
IN_PROC_BROWSER_TEST_F(ConversionDisabledBrowserTest,ConversionRegisteredWithoutOTEnabled_NoConversionDataReceived)115 IN_PROC_BROWSER_TEST_F(
116     ConversionDisabledBrowserTest,
117     ConversionRegisteredWithoutOTEnabled_NoConversionDataReceived) {
118   EXPECT_TRUE(NavigateToURL(
119       shell(),
120       embedded_test_server()->GetURL("/page_with_conversion_redirect.html")));
121   std::unique_ptr<TestConversionHost> host =
122       TestConversionHost::ReplaceAndGetConversionHost(web_contents());
123 
124   EXPECT_TRUE(ExecJs(web_contents(), "registerConversion(123)"));
125 
126   EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
127   EXPECT_EQ(0u, host->num_conversions());
128 }
129 
130 class ConversionRegistrationBrowserTest : public ConversionDisabledBrowserTest {
131  public:
132   ConversionRegistrationBrowserTest() = default;
133 
SetUpCommandLine(base::CommandLine * command_line)134   void SetUpCommandLine(base::CommandLine* command_line) override {
135     // Sets up the blink runtime feature for ConversionMeasurement.
136     command_line->AppendSwitch(
137         switches::kEnableExperimentalWebPlatformFeatures);
138   }
139 };
140 
141 // Test that full conversion path does not cause any failure when a conversion
142 // registration mojo is received.
IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,ConversionRegistration_NoCrash)143 IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,
144                        ConversionRegistration_NoCrash) {
145   EXPECT_TRUE(NavigateToURL(
146       shell(),
147       embedded_test_server()->GetURL("/page_with_conversion_redirect.html")));
148   EXPECT_TRUE(ExecJs(web_contents(), "createTrackingPixel(\"" + kWellKnownUrl +
149                                          "?conversion-data=100\");"));
150 
151   ASSERT_NO_FATAL_FAILURE(
152       EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))));
153 }
154 
IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,ConversionRegistered_ConversionDataReceived)155 IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,
156                        ConversionRegistered_ConversionDataReceived) {
157   EXPECT_TRUE(NavigateToURL(
158       shell(),
159       embedded_test_server()->GetURL("/page_with_conversion_redirect.html")));
160   std::unique_ptr<TestConversionHost> host =
161       TestConversionHost::ReplaceAndGetConversionHost(web_contents());
162 
163   EXPECT_TRUE(ExecJs(web_contents(), "registerConversion(123)"));
164   EXPECT_EQ(123UL, host->WaitForNumConversions(1));
165 }
166 
IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,FeaturePolicyDisabled_ConversionNotRegistered)167 IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,
168                        FeaturePolicyDisabled_ConversionNotRegistered) {
169   EXPECT_TRUE(NavigateToURL(
170       shell(), embedded_test_server()->GetURL(
171                    "/page_with_conversion_measurement_disabled.html")));
172   std::unique_ptr<TestConversionHost> host =
173       TestConversionHost::ReplaceAndGetConversionHost(web_contents());
174 
175   GURL redirect_url = embedded_test_server()->GetURL(
176       "/server-redirect?" + kWellKnownUrl + "?conversion-data=200");
177   ResourceLoadObserver load_observer(shell());
178   EXPECT_TRUE(ExecJs(web_contents(),
179                      JsReplace("createTrackingPixel($1);", redirect_url)));
180   load_observer.WaitForResourceCompletion(redirect_url);
181 
182   EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
183   EXPECT_EQ(0u, host->num_conversions());
184 }
185 
IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,ConversionRegistrationNotRedirect_NotReceived)186 IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,
187                        ConversionRegistrationNotRedirect_NotReceived) {
188   EXPECT_TRUE(NavigateToURL(
189       shell(),
190       embedded_test_server()->GetURL("/page_with_conversion_redirect.html")));
191   std::unique_ptr<TestConversionHost> host =
192       TestConversionHost::ReplaceAndGetConversionHost(web_contents());
193 
194   GURL registration_url = embedded_test_server()->GetURL(
195       "/" + kWellKnownUrl + "?conversion-data=200");
196 
197   // Create a load observer that will wait for the redirect to complete. If a
198   // conversion was registered, this redirect would never complete.
199   ResourceLoadObserver load_observer(shell());
200   EXPECT_TRUE(ExecJs(web_contents(),
201                      JsReplace("createTrackingPixel($1);", registration_url)));
202   load_observer.WaitForResourceCompletion(registration_url);
203 
204   // Conversion mojo messages are sent on the same message pipe as navigation
205   // messages. Because the conversion would have been sequenced prior to the
206   // navigation message, it would be observed before the NavigateToURL() call
207   // finishes.
208   EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
209   EXPECT_EQ(0u, host->num_conversions());
210 }
211 
IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,ConversionRegistrationNotSameOriginRedirect_NotReceived)212 IN_PROC_BROWSER_TEST_F(
213     ConversionRegistrationBrowserTest,
214     ConversionRegistrationNotSameOriginRedirect_NotReceived) {
215   EXPECT_TRUE(NavigateToURL(
216       shell(),
217       https_server()->GetURL("c.test", "/page_with_conversion_redirect.html")));
218   std::unique_ptr<TestConversionHost> host =
219       TestConversionHost::ReplaceAndGetConversionHost(web_contents());
220 
221   // Create a url that does the following redirect chain b.test ->
222   // a.test/.well-known/...; this conversion registration should not be allowed,
223   // a.test did not initiate the redirect to the reporting endpoint.
224   GURL redirect_url = https_server()->GetURL(
225       "a.test", "/" + kWellKnownUrl + "?conversion-data=200");
226   GURL registration_url = https_server()->GetURL(
227       "b.test", "/server-redirect?" + redirect_url.spec());
228 
229   // Create a load observer that will wait for the redirect to complete. If a
230   // conversion was registered, this redirect would never complete.
231   ResourceLoadObserver load_observer(shell());
232   EXPECT_TRUE(ExecJs(web_contents(),
233                      JsReplace("createTrackingPixel($1);", registration_url)));
234   load_observer.WaitForResourceCompletion(registration_url);
235 
236   // Conversion mojo messages are sent on the same message pipe as navigation
237   // messages. Because the conversion would have been sequenced prior to the
238   // navigation message, it would be observed before the NavigateToURL() call
239   // finishes.
240   EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
241   EXPECT_EQ(0u, host->num_conversions());
242 }
243 
IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,ConversionRegistrationIsSameOriginRedirect_Received)244 IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,
245                        ConversionRegistrationIsSameOriginRedirect_Received) {
246   EXPECT_TRUE(NavigateToURL(
247       shell(),
248       https_server()->GetURL("c.test", "/page_with_conversion_redirect.html")));
249   std::unique_ptr<TestConversionHost> host =
250       TestConversionHost::ReplaceAndGetConversionHost(web_contents());
251 
252   // Create a url that does the following redirect chain b.test -> a.test ->
253   // a.test/.well-known/...; this conversion registration should be allowed.
254   GURL well_known_url = https_server()->GetURL(
255       "a.test", "/" + kWellKnownUrl + "?conversion-data=200");
256   GURL redirect_url = https_server()->GetURL(
257       "a.test", "/server-redirect?" + well_known_url.spec());
258   GURL registration_url = https_server()->GetURL(
259       "b.test", "/server-redirect?" + redirect_url.spec());
260 
261   EXPECT_TRUE(ExecJs(web_contents(),
262                      JsReplace("createTrackingPixel($1);", registration_url)));
263   EXPECT_EQ(200UL, host->WaitForNumConversions(1));
264 }
265 
IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,ConversionRegistrationInPreload_NotReceived)266 IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,
267                        ConversionRegistrationInPreload_NotReceived) {
268   std::unique_ptr<TestConversionHost> host =
269       TestConversionHost::ReplaceAndGetConversionHost(web_contents());
270   EXPECT_TRUE(
271       NavigateToURL(shell(), embedded_test_server()->GetURL(
272                                  "/page_with_preload_conversion_ping.html")));
273 
274   EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
275   EXPECT_EQ(0u, host->num_conversions());
276 }
277 
IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,ConversionRegistrationNoData_ReceivedZero)278 IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,
279                        ConversionRegistrationNoData_ReceivedZero) {
280   EXPECT_TRUE(NavigateToURL(
281       shell(),
282       embedded_test_server()->GetURL("/page_with_conversion_redirect.html")));
283   std::unique_ptr<TestConversionHost> host =
284       TestConversionHost::ReplaceAndGetConversionHost(web_contents());
285 
286   EXPECT_TRUE(ExecJs(web_contents(), "createTrackingPixel(\"server-redirect?" +
287                                          kWellKnownUrl + "\");"));
288 
289   // Conversion data should be defaulted to 0.
290   EXPECT_EQ(0UL, host->WaitForNumConversions(1));
291 }
292 
IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,ConversionRegisteredFromChildFrame_NotReceived)293 IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,
294                        ConversionRegisteredFromChildFrame_NotReceived) {
295   EXPECT_TRUE(NavigateToURL(
296       shell(),
297       embedded_test_server()->GetURL("/page_with_subframe_conversion.html")));
298   std::unique_ptr<TestConversionHost> host =
299       TestConversionHost::ReplaceAndGetConversionHost(web_contents());
300 
301   GURL redirect_url = embedded_test_server()->GetURL(
302       "/server-redirect?" + kWellKnownUrl + "?conversion-data=200");
303   ResourceLoadObserver load_observer(shell());
304   EXPECT_TRUE(ExecJs(ChildFrameAt(web_contents()->GetMainFrame(), 0),
305                      JsReplace("createTrackingPixel($1);", redirect_url)));
306   load_observer.WaitForResourceCompletion(redirect_url);
307 
308   EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
309   EXPECT_EQ(0u, host->num_conversions());
310 }
311 
IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest,RegisterWithDifferentUrlTypes_ConversionReceivedOrIgnored)312 IN_PROC_BROWSER_TEST_F(
313     ConversionRegistrationBrowserTest,
314     RegisterWithDifferentUrlTypes_ConversionReceivedOrIgnored) {
315   const char kSecureHost[] = "a.test";
316   // TODO(crbug.com/1137113): Should include a test where an insecure request is
317   // blocked from conversion registration if it is made on a secure page. Note
318   // that this can't work for image requests due to image auto-upgrade.
319   struct {
320     std::string page_host;
321     std::string redirect_host;
322     bool expected_conversion;
323   } kTestCases[] = {
324       {"localhost" /* page_host */, "localhost" /* redirect_host */,
325        true /* conversion_expected */},
326       {"127.0.0.1" /* page_host */, "127.0.0.1" /* redirect_host */,
327        true /* conversion_expected */},
328       {"insecure.com" /* page_host */, "insecure.com" /* redirect_host */,
329        false /* conversion_expected */},
330       {kSecureHost /* page_host */, kSecureHost /* redirect_host */,
331        true /* conversion_expected */},
332       {"insecure.com" /* page_host */, kSecureHost /* redirect_host */,
333        false /* conversion_expected */}};
334 
335   for (const auto& test_case : kTestCases) {
336     std::unique_ptr<TestConversionHost> host =
337         TestConversionHost::ReplaceAndGetConversionHost(web_contents());
338 
339     // Secure hosts must be served from the https server.
340     net::EmbeddedTestServer* page_server = (test_case.page_host == kSecureHost)
341                                                ? https_server()
342                                                : embedded_test_server();
343     EXPECT_TRUE(NavigateToURL(
344         shell(), page_server->GetURL(test_case.page_host,
345                                      "/page_with_conversion_redirect.html")));
346 
347     net::EmbeddedTestServer* redirect_server =
348         (test_case.redirect_host == kSecureHost) ? https_server()
349                                                  : embedded_test_server();
350     GURL redirect_url = redirect_server->GetURL(
351         test_case.redirect_host,
352         "/server-redirect?" + kWellKnownUrl + "?conversion-data=200");
353     ResourceLoadObserver load_observer(shell());
354     EXPECT_TRUE(ExecJs(web_contents(),
355                        JsReplace("createTrackingPixel($1);", redirect_url)));
356 
357     // Either wait for a conversion redirect to be received, or wait for the url
358     // to finish loading if we are not expecting a conversions. Because
359     // conversion redirects are blocked, we do not receive completed load
360     // information for them.
361     if (test_case.expected_conversion) {
362       EXPECT_EQ(200UL, host->WaitForNumConversions(1));
363     } else {
364       load_observer.WaitForResourceCompletion(redirect_url);
365     }
366 
367     // Navigate the page. By the time the navigation finishes, we will have
368     // received any conversion mojo messages.
369     EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
370     EXPECT_EQ(test_case.expected_conversion, host->num_conversions());
371   }
372 }
373 
374 }  // namespace content
375