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