Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(120)

Side by Side Diff: content/browser/browsing_data/clear_site_data_throttle_browsertest.cc

Issue 2368923003: Support the Clear-Site-Data header on resource requests (Closed)
Patch Set: Addressed comments. Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "content/browser/browsing_data/clear_site_data_throttle.h" 5 #include "content/browser/browsing_data/clear_site_data_throttle.h"
6 6
7 #include <memory> 7 #include <memory>
8 8
9 #include "base/bind.h" 9 #include "base/bind.h"
10 #include "base/callback.h" 10 #include "base/callback.h"
11 #include "base/command_line.h" 11 #include "base/command_line.h"
12 #include "base/strings/stringprintf.h"
12 #include "content/public/browser/content_browser_client.h" 13 #include "content/public/browser/content_browser_client.h"
13 #include "content/public/browser/web_contents.h" 14 #include "content/public/browser/web_contents.h"
14 #include "content/public/common/content_switches.h" 15 #include "content/public/common/content_switches.h"
15 #include "content/public/test/content_browser_test.h" 16 #include "content/public/test/content_browser_test.h"
16 #include "content/public/test/content_browser_test_utils.h" 17 #include "content/public/test/content_browser_test_utils.h"
17 #include "content/public/test/test_navigation_observer.h" 18 #include "content/public/test/test_navigation_observer.h"
18 #include "content/shell/browser/shell.h" 19 #include "content/shell/browser/shell.h"
19 #include "net/base/escape.h" 20 #include "net/base/escape.h"
20 #include "net/base/url_util.h" 21 #include "net/base/url_util.h"
21 #include "net/dns/mock_host_resolver.h" 22 #include "net/dns/mock_host_resolver.h"
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
57 callback.Run(); 58 callback.Run();
58 } 59 }
59 }; 60 };
60 61
61 // Adds a key=value pair to the url's query. 62 // Adds a key=value pair to the url's query.
62 void AddQuery(GURL* url, const std::string& key, const std::string& value) { 63 void AddQuery(GURL* url, const std::string& key, const std::string& value) {
63 *url = GURL(url->spec() + (url->has_query() ? "&" : "?") + key + "=" + 64 *url = GURL(url->spec() + (url->has_query() ? "&" : "?") + key + "=" +
64 net::EscapeQueryParamValue(value, false)); 65 net::EscapeQueryParamValue(value, false));
65 } 66 }
66 67
68 // A helper function to synchronize with JS side of the tests. JS can append
69 // information to the loaded website's URL hash and C++ will wait until that
70 // happens.
71 // TODO(msramek): Find a solution without polling.
mmenke 2016/10/13 17:16:31 Use TitleWatcher from browser_test_utils.h instead
msramek 2016/10/17 14:28:31 Done. Nice! I knew that I couldn't be the first on
72 void WaitForHashInUrl(Shell* shell, const std::string& hash) {
73 while (true) {
74 if (shell->web_contents()->GetLastCommittedURL().ref_piece() == hash)
75 break;
76
77 base::RunLoop run_loop;
78 run_loop.RunUntilIdle();
79 }
80 }
81
67 // A value of the Clear-Site-Data header that requests cookie deletion. Reused 82 // A value of the Clear-Site-Data header that requests cookie deletion. Reused
68 // in tests that need a valid header but do not depend on its value. 83 // in tests that need a valid header but do not depend on its value.
69 static const char* kClearCookiesHeader = "{ \"types\": [ \"cookies\" ] }"; 84 static const char* kClearCookiesHeader = "{ \"types\": [ \"cookies\" ] }";
70 85
71 } // namespace 86 } // namespace
72 87
73 class ClearSiteDataThrottleBrowserTest : public ContentBrowserTest { 88 class ClearSiteDataThrottleBrowserTest : public ContentBrowserTest {
74 public: 89 public:
75 void SetUpCommandLine(base::CommandLine* command_line) override { 90 void SetUpCommandLine(base::CommandLine* command_line) override {
76 ContentBrowserTest::SetUpCommandLine(command_line); 91 ContentBrowserTest::SetUpCommandLine(command_line);
(...skipping 25 matching lines...) Expand all
102 base::Bind(&ClearSiteDataThrottleBrowserTest::HandleRequest, 117 base::Bind(&ClearSiteDataThrottleBrowserTest::HandleRequest,
103 base::Unretained(this))); 118 base::Unretained(this)));
104 ASSERT_TRUE(https_server_->Start()); 119 ASSERT_TRUE(https_server_->Start());
105 } 120 }
106 121
107 TestContentBrowserClient* GetContentBrowserClient() { return &test_client_; } 122 TestContentBrowserClient* GetContentBrowserClient() { return &test_client_; }
108 123
109 net::EmbeddedTestServer* https_server() { return https_server_.get(); } 124 net::EmbeddedTestServer* https_server() { return https_server_.get(); }
110 125
111 private: 126 private:
112 // Handles all requests. If the request url query contains a "header" key, 127 // Handles all requests.
113 // responds with the "Clear-Site-Data" header of the corresponding value. 128 //
114 // If the query contains a "redirect" key, responds with a redirect to a url 129 // Supports the following <key>=<value> query parameters in the url:
115 // given by the corresponding value. 130 // <key>="header" responds with the header "Clear-Site-Data: <value>"
131 // <key>="redirect" responds with a redirect to the url <value>
132 // <key>="html" responds with a text/html content <value>
133 // <key>="file" responds with the content of file <value>
116 // 134 //
117 // Example: "https://localhost/?header={}&redirect=example.com" will respond 135 // Example: "https://localhost/?header={}&redirect=example.com" will respond
118 // with headers 136 // with headers
119 // Clear-Site-Data: {} 137 // Clear-Site-Data: {}
120 // Location: example.com 138 // Location: example.com
139 //
140 // Example: "https://localhost/?html=<html><head></head><body></body></html>"
141 // will respond with the header
142 // Content-Type: text/html
143 // and content
144 // <html><head></head><body></body></html>
145 //
146 // Example: "https://localhost/?file=file.html
147 // will respond with the header
148 // Content-Type: text/html
149 // and content from the file content/test/data/file.html
121 std::unique_ptr<net::test_server::HttpResponse> HandleRequest( 150 std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
122 const net::test_server::HttpRequest& request) { 151 const net::test_server::HttpRequest& request) {
123 std::unique_ptr<net::test_server::BasicHttpResponse> response( 152 std::unique_ptr<net::test_server::BasicHttpResponse> response(
124 new net::test_server::BasicHttpResponse()); 153 new net::test_server::BasicHttpResponse());
125 154
126 std::string value; 155 std::string value;
127 if (net::GetValueForKeyInQuery(request.GetURL(), "header", &value)) 156 if (net::GetValueForKeyInQuery(request.GetURL(), "header", &value))
128 response->AddCustomHeader("Clear-Site-Data", value); 157 response->AddCustomHeader("Clear-Site-Data", value);
129 158
130 if (net::GetValueForKeyInQuery(request.GetURL(), "redirect", &value)) { 159 if (net::GetValueForKeyInQuery(request.GetURL(), "redirect", &value)) {
131 response->set_code(net::HTTP_FOUND); 160 response->set_code(net::HTTP_FOUND);
132 response->AddCustomHeader("Location", value); 161 response->AddCustomHeader("Location", value);
133 } else { 162 } else {
134 response->set_code(net::HTTP_OK); 163 response->set_code(net::HTTP_OK);
135 } 164 }
136 165
166 if (net::GetValueForKeyInQuery(request.GetURL(), "html", &value)) {
167 response->set_content_type("text/html");
168 response->set_content(value);
169
170 // We're telling the server what to serve, and the XSS auditor will
mmenke 2016/10/13 17:16:31 nit: Avoid "we" in comments, due to ambiguity.
msramek 2016/10/17 14:28:31 Done.
171 // complain if |value| contains JS code. Disable that protection.
172 response->AddCustomHeader("X-XSS-Protection", "0");
173 }
174
175 if (net::GetValueForKeyInQuery(request.GetURL(), "file", &value)) {
176 base::FilePath path(GetTestFilePath("browsing_data", value.c_str()));
177 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
178 EXPECT_TRUE(file.IsValid());
179 int64_t length = file.GetLength();
180 EXPECT_GE(length, 0);
181 std::unique_ptr<char[]> buffer(new char[length + 1]);
182 file.Read(0, buffer.get(), length);
183 buffer[length] = '\0';
184
185 if (path.Extension() == FILE_PATH_LITERAL(".js"))
186 response->set_content_type("application/javascript");
187 else if (path.Extension() == FILE_PATH_LITERAL(".html"))
188 response->set_content_type("text/html");
189 else
190 NOTREACHED();
191
192 response->set_content(buffer.get());
193 }
194
137 return std::move(response); 195 return std::move(response);
138 } 196 }
139 197
140 TestContentBrowserClient test_client_; 198 TestContentBrowserClient test_client_;
141 std::unique_ptr<net::EmbeddedTestServer> https_server_; 199 std::unique_ptr<net::EmbeddedTestServer> https_server_;
142 }; 200 };
143 201
144 // Tests that the header is recognized on the beginning, in the middle, and on 202 // Tests that the header is recognized on the beginning, in the middle, and on
145 // the end of a redirect chain. Each of the three parts of the chain may or 203 // the end of a navigation redirect chain. Each of the three parts of the chain
146 // may not send the header, so there are 8 configurations to test. 204 // may or may not send the header, so there are 8 configurations to test.
147 IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, Redirect) { 205 IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, RedirectNavigation) {
148 GURL base_urls[3] = { 206 GURL base_urls[3] = {
149 https_server()->GetURL("origin1.com", "/"), 207 https_server()->GetURL("origin1.com", "/"),
150 https_server()->GetURL("origin2.com", "/foo/bar"), 208 https_server()->GetURL("origin2.com", "/foo/bar"),
151 https_server()->GetURL("origin3.com", "/index.html"), 209 https_server()->GetURL("origin3.com", "/index.html"),
152 }; 210 };
153 211
154 // Iterate through the configurations. URLs whose index is matched by the mask 212 // Iterate through the configurations. URLs whose index is matched by the mask
155 // will send the header, the others won't. 213 // will send the header, the others won't.
156 for (int mask = 0; mask < (1 << 3); ++mask) { 214 for (int mask = 0; mask < (1 << 3); ++mask) {
157 GURL urls[3]; 215 GURL urls[3];
(...skipping 17 matching lines...) Expand all
175 // Navigate to the first url of the redirect chain. 233 // Navigate to the first url of the redirect chain.
176 NavigateToURL(shell(), urls[0]); 234 NavigateToURL(shell(), urls[0]);
177 235
178 // We reached the end of the redirect chain. 236 // We reached the end of the redirect chain.
179 EXPECT_EQ(urls[2], shell()->web_contents()->GetURL()); 237 EXPECT_EQ(urls[2], shell()->web_contents()->GetURL());
180 238
181 testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient()); 239 testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient());
182 } 240 }
183 } 241 }
184 242
243 // Tests that the header is recognized on the beginning, in the middle, and on
244 // the end of a resource load redirect chain. Each of the three parts of the
245 // chain may or may not send the header, so there are 8 configurations to test.
246 IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, RedirectResourceLoad) {
247 GURL base_urls[3] = {
248 https_server()->GetURL("origin1.com", "/image.png"),
249 https_server()->GetURL("origin2.com", "/redirected-image.png"),
250 https_server()->GetURL("origin3.com", "/actual-image.png"),
251 };
252
253 // Iterate through the configurations. URLs whose index is matched by the mask
254 // will send the header, the others won't.
255 for (int mask = 0; mask < (1 << 3); ++mask) {
256 GURL urls[3];
257
258 // Set up the expectations.
259 for (int i = 0; i < 3; ++i) {
260 urls[i] = base_urls[i];
261 if (mask & (1 << i))
262 AddQuery(&urls[i], "header", kClearCookiesHeader);
263
264 EXPECT_CALL(*GetContentBrowserClient(),
265 ClearSiteData(shell()->web_contents()->GetBrowserContext(),
266 url::Origin(urls[i]), _, _, _, _))
267 .Times((mask & (1 << i)) ? 1 : 0);
268 }
269
270 // Set up redirects between urls 0 --> 1 --> 2.
271 AddQuery(&urls[1], "redirect", urls[2].spec());
272 AddQuery(&urls[0], "redirect", urls[1].spec());
273
274 // Navigate to a page that embeds "https://origin1.com/image.png"
275 // and observe the loading of that resource.
276 GURL page_with_image = https_server()->GetURL("origin4.com", "/index.html");
277 std::string content_with_image =
278 "<html><head></head><body>"
279 "<img src=\"" +
280 urls[0].spec() +
281 "\" />"
282 "</body></html>";
283 AddQuery(&page_with_image, "html", content_with_image);
284 NavigateToURL(shell(), page_with_image);
285
286 testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient());
287 }
288 }
289
185 // Tests that the Clear-Site-Data header is ignored for insecure origins. 290 // Tests that the Clear-Site-Data header is ignored for insecure origins.
186 IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, Insecure) { 291 IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, InsecureNavigation) {
187 // ClearSiteData() should not be called on HTTP. 292 // ClearSiteData() should not be called on HTTP.
188 GURL url = embedded_test_server()->GetURL("example.com", "/"); 293 GURL url = embedded_test_server()->GetURL("example.com", "/");
189 AddQuery(&url, "header", kClearCookiesHeader); 294 AddQuery(&url, "header", kClearCookiesHeader);
190 ASSERT_FALSE(url.SchemeIsCryptographic()); 295 ASSERT_FALSE(url.SchemeIsCryptographic());
191 296
192 EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _)) 297 EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _))
193 .Times(0); 298 .Times(0);
194 299
195 NavigateToURL(shell(), url); 300 NavigateToURL(shell(), url);
196 } 301 }
197 302
303 // Tests that the Clear-Site-Data header is honored for secure resource loads
304 // and ignored for insecure ones.
305 IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest,
306 SecureAndInsecureResourceLoad) {
307 GURL insecure_image =
308 embedded_test_server()->GetURL("example.com", "/image.png");
309 GURL secure_image = https_server()->GetURL("example.com", "/image.png");
310
311 ASSERT_TRUE(secure_image.SchemeIsCryptographic());
312 ASSERT_FALSE(insecure_image.SchemeIsCryptographic());
313
314 AddQuery(&secure_image, "header", kClearCookiesHeader);
315 AddQuery(&insecure_image, "header", kClearCookiesHeader);
316
317 std::string content_with_insecure_image =
318 "<html><head></head><body>"
319 "<img src=\"" +
320 insecure_image.spec() +
321 "\" />"
322 "</body></html>";
323
324 std::string content_with_secure_image =
325 "<html><head></head><body>"
326 "<img src=\"" +
327 secure_image.spec() +
328 "\" />"
329 "</body></html>";
330
331 // Test insecure resources.
332 GURL insecure_page = embedded_test_server()->GetURL("example.com", "/");
333 GURL secure_page = https_server()->GetURL("example.com", "/");
334
335 AddQuery(&insecure_page, "html", content_with_insecure_image);
336 AddQuery(&secure_page, "html", content_with_insecure_image);
337
338 EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _))
339 .Times(0);
340
341 // Insecure resource on an insecure page does not execute Clear-Site-Data.
342 NavigateToURL(shell(), insecure_page);
343
344 // Insecure resource on a secure page does not execute Clear-Site-Data.
345 NavigateToURL(shell(), secure_page);
346
347 testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient());
348
349 // Test secure resources.
350 insecure_page = embedded_test_server()->GetURL("example.com", "/");
351 secure_page = https_server()->GetURL("example.com", "/");
352
353 AddQuery(&insecure_page, "html", content_with_secure_image);
354 AddQuery(&secure_page, "html", content_with_secure_image);
355
356 // Secure resource on an insecure page does execute Clear-Site-Data.
357 EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _))
358 .Times(1);
359
360 NavigateToURL(shell(), secure_page);
361 testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient());
362
363 // Secure resource on a secure page does execute Clear-Site-Data.
364 EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _))
365 .Times(1);
366
367 NavigateToURL(shell(), secure_page);
368 }
369
370 // Tests that the Clear-Site-Data header is ignored for service worker resource
371 // loads. Specifically, we test it on a cross-origin request; the header is
372 // ignored for same-origin requests as well, but there it isn't harmful.
373 IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, ServiceWorker) {
374 GURL origin1 = https_server()->GetURL("origin1.com", "/");
375 GURL origin2 = https_server()->GetURL("origin2.com", "/");
376
377 // We will load |origin1| and make cross-origin requests for |origin2| from
378 // there. We need to inform the JS side about |origin2| using a query
379 // parameter; it cannot be hardcoded in the JS code, as the port number
380 // of the test server is chosen randomly.
381 GURL url = origin1;
382 AddQuery(&url, "file", "worker_setup.html");
383 AddQuery(&url, "other_origin", origin2.spec());
384
385 // Navigation to worker_setup.html will first request a resource with the
386 // Clear-Site-Data header. This is done for control - to make sure that
387 // the second part of this test will ignore the header because it's processed
388 // by a service worker, and not because the test is set up incorrectly.
389 EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _))
390 .Times(1);
391 NavigateToURL(shell(), url);
392
393 // After the resource was fetched, a service worker should have been
394 // installed. Since the installation of a service worker is asynchronous,
395 // the JS side will inform us about it in its URL hash.
396 WaitForHashInUrl(shell(), "service-worker-registered");
397 testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient());
398
399 // The service worker will now serve a page containing two images, and
400 // fetching those two images will again return the Clear-Site-Data header.
401 // This time, it should be ignored.
402 EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _))
403 .Times(0);
404
405 url = https_server()->GetURL("origin1.com", "/anything-in-workers-scope");
406 AddQuery(&url, "other_origin", origin2.spec());
407 NavigateToURL(shell(), url);
408 WaitForHashInUrl(shell(), "service-worker-active");
409 }
410
411 // Tests that Clear-Site-Data is only executed on a resource fetch
412 // if credentials are allowed in that fetch.
413 IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, Credentials) {
414 GURL page_template = https_server()->GetURL("origin1.com", "/");
415 GURL same_origin_resource =
416 https_server()->GetURL("origin1.com", "/resource");
417 GURL different_origin_resource =
418 https_server()->GetURL("origin2.com", "/resource");
419
420 AddQuery(&same_origin_resource, "header", kClearCookiesHeader);
421 AddQuery(&different_origin_resource, "header", kClearCookiesHeader);
422
423 struct TestCase {
424 bool same_origin;
425 std::string credentials;
426 bool should_run;
427 } test_cases[] = {
mmenke 2016/10/13 17:16:31 const kTestCases?
msramek 2016/10/17 14:28:31 Done.
428 { true, "", false },
429 { true, "omit", false },
430 { true, "same-origin", true },
431 { true, "include", true },
432 { false, "", false },
433 { false, "omit", false },
434 { false, "same-origin", false },
435 { false, "include", true },
436 };
437
438 for (struct TestCase& test_case : test_cases) {
mmenke 2016/10/13 17:16:31 const TestCase& (struct isn't needed, const seems
msramek 2016/10/17 14:28:31 Done. Yes, that's what I wanted to write :)
439 const GURL& resource = test_case.same_origin
440 ? same_origin_resource : different_origin_resource;
441 std::string credentials = test_case.credentials.empty()
442 ? "" : "credentials: '" + test_case.credentials + "'";
443
444 // Fetch a resource. Note that we also handle fetch() error, as we will get
mmenke 2016/10/13 17:16:31 nit: --we (x2)
msramek 2016/10/17 14:28:31 Done.
445 // it for third-party fetches because of missing
446 // Access-Control-Allow-Origin. However, that only affects the visibility
447 // of the response; the header will still be processed.
448 std::string content = base::StringPrintf(
449 "<html><head></head><body><script>"
450 "fetch('%s', {%s})"
451 ".then(function() { window.location.hash = 'done'; })"
452 ".catch(function() { window.location.hash = 'done'; })"
453 "</script></body></html>",
454 resource.spec().c_str(),
455 credentials.c_str());
456
457 GURL page = page_template;
458 AddQuery(&page, "html", content);
459
460 EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _))
461 .Times(test_case.should_run ? 1 : 0);
462 NavigateToURL(shell(), page);
463 WaitForHashInUrl(shell(), "done");
464 testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient());
465 }
466 }
467
198 // Tests that ClearSiteData() is called for the correct datatypes. 468 // Tests that ClearSiteData() is called for the correct datatypes.
199 IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, Types) { 469 IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, Types) {
200 GURL base_url = https_server()->GetURL("example.com", "/"); 470 GURL base_url = https_server()->GetURL("example.com", "/");
201 471
202 struct TestCase { 472 struct TestCase {
203 const char* value; 473 const char* value;
204 bool remove_cookies; 474 bool remove_cookies;
205 bool remove_storage; 475 bool remove_storage;
206 bool remove_cache; 476 bool remove_cache;
207 } test_cases[] = { 477 } test_cases[] = {
(...skipping 17 matching lines...) Expand all
225 url::Origin(url), test_case.remove_cookies, 495 url::Origin(url), test_case.remove_cookies,
226 test_case.remove_storage, test_case.remove_cache, _)); 496 test_case.remove_storage, test_case.remove_cache, _));
227 497
228 NavigateToURL(shell(), url); 498 NavigateToURL(shell(), url);
229 499
230 testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient()); 500 testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient());
231 } 501 }
232 } 502 }
233 503
234 } // namespace content 504 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698