Chromium Code Reviews| Index: content/browser/browsing_data/clear_site_data_throttle_browsertest.cc |
| diff --git a/content/browser/browsing_data/clear_site_data_throttle_browsertest.cc b/content/browser/browsing_data/clear_site_data_throttle_browsertest.cc |
| index f5e43643bc67802e5fd264bf70fd51cd243394c3..4ccedb3593b54b196d5bda21546fb48a8740a2c0 100644 |
| --- a/content/browser/browsing_data/clear_site_data_throttle_browsertest.cc |
| +++ b/content/browser/browsing_data/clear_site_data_throttle_browsertest.cc |
| @@ -9,6 +9,7 @@ |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| +#include "base/strings/stringprintf.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_switches.h" |
| @@ -64,6 +65,20 @@ void AddQuery(GURL* url, const std::string& key, const std::string& value) { |
| net::EscapeQueryParamValue(value, false)); |
| } |
| +// A helper function to synchronize with JS side of the tests. JS can append |
| +// information to the loaded website's URL hash and C++ will wait until that |
| +// happens. |
| +// 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
|
| +void WaitForHashInUrl(Shell* shell, const std::string& hash) { |
| + while (true) { |
| + if (shell->web_contents()->GetLastCommittedURL().ref_piece() == hash) |
| + break; |
| + |
| + base::RunLoop run_loop; |
| + run_loop.RunUntilIdle(); |
| + } |
| +} |
| + |
| // A value of the Clear-Site-Data header that requests cookie deletion. Reused |
| // in tests that need a valid header but do not depend on its value. |
| static const char* kClearCookiesHeader = "{ \"types\": [ \"cookies\" ] }"; |
| @@ -109,15 +124,29 @@ class ClearSiteDataThrottleBrowserTest : public ContentBrowserTest { |
| net::EmbeddedTestServer* https_server() { return https_server_.get(); } |
| private: |
| - // Handles all requests. If the request url query contains a "header" key, |
| - // responds with the "Clear-Site-Data" header of the corresponding value. |
| - // If the query contains a "redirect" key, responds with a redirect to a url |
| - // given by the corresponding value. |
| + // Handles all requests. |
| + // |
| + // Supports the following <key>=<value> query parameters in the url: |
| + // <key>="header" responds with the header "Clear-Site-Data: <value>" |
| + // <key>="redirect" responds with a redirect to the url <value> |
| + // <key>="html" responds with a text/html content <value> |
| + // <key>="file" responds with the content of file <value> |
| // |
| // Example: "https://localhost/?header={}&redirect=example.com" will respond |
| // with headers |
| // Clear-Site-Data: {} |
| // Location: example.com |
| + // |
| + // Example: "https://localhost/?html=<html><head></head><body></body></html>" |
| + // will respond with the header |
| + // Content-Type: text/html |
| + // and content |
| + // <html><head></head><body></body></html> |
| + // |
| + // Example: "https://localhost/?file=file.html |
| + // will respond with the header |
| + // Content-Type: text/html |
| + // and content from the file content/test/data/file.html |
| std::unique_ptr<net::test_server::HttpResponse> HandleRequest( |
| const net::test_server::HttpRequest& request) { |
| std::unique_ptr<net::test_server::BasicHttpResponse> response( |
| @@ -134,6 +163,35 @@ class ClearSiteDataThrottleBrowserTest : public ContentBrowserTest { |
| response->set_code(net::HTTP_OK); |
| } |
| + if (net::GetValueForKeyInQuery(request.GetURL(), "html", &value)) { |
| + response->set_content_type("text/html"); |
| + response->set_content(value); |
| + |
| + // 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.
|
| + // complain if |value| contains JS code. Disable that protection. |
| + response->AddCustomHeader("X-XSS-Protection", "0"); |
| + } |
| + |
| + if (net::GetValueForKeyInQuery(request.GetURL(), "file", &value)) { |
| + base::FilePath path(GetTestFilePath("browsing_data", value.c_str())); |
| + base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| + EXPECT_TRUE(file.IsValid()); |
| + int64_t length = file.GetLength(); |
| + EXPECT_GE(length, 0); |
| + std::unique_ptr<char[]> buffer(new char[length + 1]); |
| + file.Read(0, buffer.get(), length); |
| + buffer[length] = '\0'; |
| + |
| + if (path.Extension() == FILE_PATH_LITERAL(".js")) |
| + response->set_content_type("application/javascript"); |
| + else if (path.Extension() == FILE_PATH_LITERAL(".html")) |
| + response->set_content_type("text/html"); |
| + else |
| + NOTREACHED(); |
| + |
| + response->set_content(buffer.get()); |
| + } |
| + |
| return std::move(response); |
| } |
| @@ -142,9 +200,9 @@ class ClearSiteDataThrottleBrowserTest : public ContentBrowserTest { |
| }; |
| // Tests that the header is recognized on the beginning, in the middle, and on |
| -// the end of a redirect chain. Each of the three parts of the chain may or |
| -// may not send the header, so there are 8 configurations to test. |
| -IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, Redirect) { |
| +// the end of a navigation redirect chain. Each of the three parts of the chain |
| +// may or may not send the header, so there are 8 configurations to test. |
| +IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, RedirectNavigation) { |
| GURL base_urls[3] = { |
| https_server()->GetURL("origin1.com", "/"), |
| https_server()->GetURL("origin2.com", "/foo/bar"), |
| @@ -182,8 +240,55 @@ IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, Redirect) { |
| } |
| } |
| +// Tests that the header is recognized on the beginning, in the middle, and on |
| +// the end of a resource load redirect chain. Each of the three parts of the |
| +// chain may or may not send the header, so there are 8 configurations to test. |
| +IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, RedirectResourceLoad) { |
| + GURL base_urls[3] = { |
| + https_server()->GetURL("origin1.com", "/image.png"), |
| + https_server()->GetURL("origin2.com", "/redirected-image.png"), |
| + https_server()->GetURL("origin3.com", "/actual-image.png"), |
| + }; |
| + |
| + // Iterate through the configurations. URLs whose index is matched by the mask |
| + // will send the header, the others won't. |
| + for (int mask = 0; mask < (1 << 3); ++mask) { |
| + GURL urls[3]; |
| + |
| + // Set up the expectations. |
| + for (int i = 0; i < 3; ++i) { |
| + urls[i] = base_urls[i]; |
| + if (mask & (1 << i)) |
| + AddQuery(&urls[i], "header", kClearCookiesHeader); |
| + |
| + EXPECT_CALL(*GetContentBrowserClient(), |
| + ClearSiteData(shell()->web_contents()->GetBrowserContext(), |
| + url::Origin(urls[i]), _, _, _, _)) |
| + .Times((mask & (1 << i)) ? 1 : 0); |
| + } |
| + |
| + // Set up redirects between urls 0 --> 1 --> 2. |
| + AddQuery(&urls[1], "redirect", urls[2].spec()); |
| + AddQuery(&urls[0], "redirect", urls[1].spec()); |
| + |
| + // Navigate to a page that embeds "https://origin1.com/image.png" |
| + // and observe the loading of that resource. |
| + GURL page_with_image = https_server()->GetURL("origin4.com", "/index.html"); |
| + std::string content_with_image = |
| + "<html><head></head><body>" |
| + "<img src=\"" + |
| + urls[0].spec() + |
| + "\" />" |
| + "</body></html>"; |
| + AddQuery(&page_with_image, "html", content_with_image); |
| + NavigateToURL(shell(), page_with_image); |
| + |
| + testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient()); |
| + } |
| +} |
| + |
| // Tests that the Clear-Site-Data header is ignored for insecure origins. |
| -IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, Insecure) { |
| +IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, InsecureNavigation) { |
| // ClearSiteData() should not be called on HTTP. |
| GURL url = embedded_test_server()->GetURL("example.com", "/"); |
| AddQuery(&url, "header", kClearCookiesHeader); |
| @@ -195,6 +300,171 @@ IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, Insecure) { |
| NavigateToURL(shell(), url); |
| } |
| +// Tests that the Clear-Site-Data header is honored for secure resource loads |
| +// and ignored for insecure ones. |
| +IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, |
| + SecureAndInsecureResourceLoad) { |
| + GURL insecure_image = |
| + embedded_test_server()->GetURL("example.com", "/image.png"); |
| + GURL secure_image = https_server()->GetURL("example.com", "/image.png"); |
| + |
| + ASSERT_TRUE(secure_image.SchemeIsCryptographic()); |
| + ASSERT_FALSE(insecure_image.SchemeIsCryptographic()); |
| + |
| + AddQuery(&secure_image, "header", kClearCookiesHeader); |
| + AddQuery(&insecure_image, "header", kClearCookiesHeader); |
| + |
| + std::string content_with_insecure_image = |
| + "<html><head></head><body>" |
| + "<img src=\"" + |
| + insecure_image.spec() + |
| + "\" />" |
| + "</body></html>"; |
| + |
| + std::string content_with_secure_image = |
| + "<html><head></head><body>" |
| + "<img src=\"" + |
| + secure_image.spec() + |
| + "\" />" |
| + "</body></html>"; |
| + |
| + // Test insecure resources. |
| + GURL insecure_page = embedded_test_server()->GetURL("example.com", "/"); |
| + GURL secure_page = https_server()->GetURL("example.com", "/"); |
| + |
| + AddQuery(&insecure_page, "html", content_with_insecure_image); |
| + AddQuery(&secure_page, "html", content_with_insecure_image); |
| + |
| + EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _)) |
| + .Times(0); |
| + |
| + // Insecure resource on an insecure page does not execute Clear-Site-Data. |
| + NavigateToURL(shell(), insecure_page); |
| + |
| + // Insecure resource on a secure page does not execute Clear-Site-Data. |
| + NavigateToURL(shell(), secure_page); |
| + |
| + testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient()); |
| + |
| + // Test secure resources. |
| + insecure_page = embedded_test_server()->GetURL("example.com", "/"); |
| + secure_page = https_server()->GetURL("example.com", "/"); |
| + |
| + AddQuery(&insecure_page, "html", content_with_secure_image); |
| + AddQuery(&secure_page, "html", content_with_secure_image); |
| + |
| + // Secure resource on an insecure page does execute Clear-Site-Data. |
| + EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _)) |
| + .Times(1); |
| + |
| + NavigateToURL(shell(), secure_page); |
| + testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient()); |
| + |
| + // Secure resource on a secure page does execute Clear-Site-Data. |
| + EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _)) |
| + .Times(1); |
| + |
| + NavigateToURL(shell(), secure_page); |
| +} |
| + |
| +// Tests that the Clear-Site-Data header is ignored for service worker resource |
| +// loads. Specifically, we test it on a cross-origin request; the header is |
| +// ignored for same-origin requests as well, but there it isn't harmful. |
| +IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, ServiceWorker) { |
| + GURL origin1 = https_server()->GetURL("origin1.com", "/"); |
| + GURL origin2 = https_server()->GetURL("origin2.com", "/"); |
| + |
| + // We will load |origin1| and make cross-origin requests for |origin2| from |
| + // there. We need to inform the JS side about |origin2| using a query |
| + // parameter; it cannot be hardcoded in the JS code, as the port number |
| + // of the test server is chosen randomly. |
| + GURL url = origin1; |
| + AddQuery(&url, "file", "worker_setup.html"); |
| + AddQuery(&url, "other_origin", origin2.spec()); |
| + |
| + // Navigation to worker_setup.html will first request a resource with the |
| + // Clear-Site-Data header. This is done for control - to make sure that |
| + // the second part of this test will ignore the header because it's processed |
| + // by a service worker, and not because the test is set up incorrectly. |
| + EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _)) |
| + .Times(1); |
| + NavigateToURL(shell(), url); |
| + |
| + // After the resource was fetched, a service worker should have been |
| + // installed. Since the installation of a service worker is asynchronous, |
| + // the JS side will inform us about it in its URL hash. |
| + WaitForHashInUrl(shell(), "service-worker-registered"); |
| + testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient()); |
| + |
| + // The service worker will now serve a page containing two images, and |
| + // fetching those two images will again return the Clear-Site-Data header. |
| + // This time, it should be ignored. |
| + EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _)) |
| + .Times(0); |
| + |
| + url = https_server()->GetURL("origin1.com", "/anything-in-workers-scope"); |
| + AddQuery(&url, "other_origin", origin2.spec()); |
| + NavigateToURL(shell(), url); |
| + WaitForHashInUrl(shell(), "service-worker-active"); |
| +} |
| + |
| +// Tests that Clear-Site-Data is only executed on a resource fetch |
| +// if credentials are allowed in that fetch. |
| +IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, Credentials) { |
| + GURL page_template = https_server()->GetURL("origin1.com", "/"); |
| + GURL same_origin_resource = |
| + https_server()->GetURL("origin1.com", "/resource"); |
| + GURL different_origin_resource = |
| + https_server()->GetURL("origin2.com", "/resource"); |
| + |
| + AddQuery(&same_origin_resource, "header", kClearCookiesHeader); |
| + AddQuery(&different_origin_resource, "header", kClearCookiesHeader); |
| + |
| + struct TestCase { |
| + bool same_origin; |
| + std::string credentials; |
| + bool should_run; |
| + } test_cases[] = { |
|
mmenke
2016/10/13 17:16:31
const kTestCases?
msramek
2016/10/17 14:28:31
Done.
|
| + { true, "", false }, |
| + { true, "omit", false }, |
| + { true, "same-origin", true }, |
| + { true, "include", true }, |
| + { false, "", false }, |
| + { false, "omit", false }, |
| + { false, "same-origin", false }, |
| + { false, "include", true }, |
| + }; |
| + |
| + 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 :)
|
| + const GURL& resource = test_case.same_origin |
| + ? same_origin_resource : different_origin_resource; |
| + std::string credentials = test_case.credentials.empty() |
| + ? "" : "credentials: '" + test_case.credentials + "'"; |
| + |
| + // 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.
|
| + // it for third-party fetches because of missing |
| + // Access-Control-Allow-Origin. However, that only affects the visibility |
| + // of the response; the header will still be processed. |
| + std::string content = base::StringPrintf( |
| + "<html><head></head><body><script>" |
| + "fetch('%s', {%s})" |
| + ".then(function() { window.location.hash = 'done'; })" |
| + ".catch(function() { window.location.hash = 'done'; })" |
| + "</script></body></html>", |
| + resource.spec().c_str(), |
| + credentials.c_str()); |
| + |
| + GURL page = page_template; |
| + AddQuery(&page, "html", content); |
| + |
| + EXPECT_CALL(*GetContentBrowserClient(), ClearSiteData(_, _, _, _, _, _)) |
| + .Times(test_case.should_run ? 1 : 0); |
| + NavigateToURL(shell(), page); |
| + WaitForHashInUrl(shell(), "done"); |
| + testing::Mock::VerifyAndClearExpectations(GetContentBrowserClient()); |
| + } |
| +} |
| + |
| // Tests that ClearSiteData() is called for the correct datatypes. |
| IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, Types) { |
| GURL base_url = https_server()->GetURL("example.com", "/"); |