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..fd3d73f0d1ced341c6fd315f58c2dd66a2680cd8 100644 |
--- a/content/browser/browsing_data/clear_site_data_throttle_browsertest.cc |
+++ b/content/browser/browsing_data/clear_site_data_throttle_browsertest.cc |
@@ -9,9 +9,12 @@ |
#include "base/bind.h" |
#include "base/callback.h" |
#include "base/command_line.h" |
+#include "base/strings/stringprintf.h" |
+#include "base/strings/utf_string_conversions.h" |
#include "content/public/browser/content_browser_client.h" |
#include "content/public/browser/web_contents.h" |
#include "content/public/common/content_switches.h" |
+#include "content/public/test/browser_test_utils.h" |
#include "content/public/test/content_browser_test.h" |
#include "content/public/test/content_browser_test_utils.h" |
#include "content/public/test/test_navigation_observer.h" |
@@ -64,6 +67,15 @@ 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 title and C++ will wait until that |
+// happens. |
+void WaitForTitle(const Shell* shell, const char* expected_title) { |
+ base::string16 expected_title_16 = base::ASCIIToUTF16(expected_title); |
+ TitleWatcher title_watcher(shell->web_contents(), expected_title_16); |
+ ASSERT_EQ(expected_title_16, title_watcher.WaitAndGetTitle()); |
+} |
+ |
// 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 +121,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 +160,36 @@ 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); |
+ |
+ // The "html" parameter is telling the server what to serve, and the XSS |
+ // auditor will complain if its |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 +198,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 +238,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 +298,184 @@ 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. |
+IN_PROC_BROWSER_TEST_F(ClearSiteDataThrottleBrowserTest, ServiceWorker) { |
+ GURL origin1 = https_server()->GetURL("origin1.com", "/"); |
+ GURL origin2 = https_server()->GetURL("origin2.com", "/"); |
+ GURL origin3 = https_server()->GetURL("origin3.com", "/"); |
+ GURL origin4 = https_server()->GetURL("origin4.com", "/"); |
+ |
+ // Navigation to worker_setup.html will install a service worker. Since |
+ // the installation is asynchronous, the JS side will inform us about it in |
+ // the page title. |
+ GURL url = origin1; |
+ AddQuery(&url, "file", "worker_setup.html"); |
+ NavigateToURL(shell(), url); |
+ WaitForTitle(shell(), "service worker registered"); |
+ |
+ // The service worker will now serve a page containing several images, which |
+ // the browser will try to fetch. The service worker will be instructed |
+ // to handle some of the fetches itself, while others will be handled by |
+ // the testing server. The setup is the following: |
+ // |
+ // origin1.com/resource (-> server; should respect header) |
+ // origin1.com/resource_from_sw (-> service worker; should not respect header) |
+ // origin2.com/resource_from_sw (-> service worker; should not respect header) |
+ // origin2.com/resource (-> server; should respect header) |
+ // origin3.com/resource_from_sw (-> service worker; should not respect header) |
+ // origin3.com/resource_from_sw (-> service worker; should not respect header) |
+ // origin4.com/resource (-> server; should respect header) |
+ // origin4.com/resource (-> server; should respect header) |
+ // |
+ // |origin1| and |origin2| are used to test that there is no difference |
+ // between same-origin and third-party fetches. Clear-Site-Data should be |
+ // called once for each of these origins - caused by the "/resource" fetch, |
+ // but not by the "/resource_from_sw" fetch. |origin3| and |origin4| prove |
+ // that the number of calls is dependent on the number of network responses, |
+ // i.e. that it isn't always 1 as in the case of |origin1| and |origin2|. |
falken
2016/10/19 07:14:38
Nice documentation!
msramek
2016/10/19 12:20:12
Thanks!
|
+ EXPECT_CALL(*GetContentBrowserClient(), |
+ ClearSiteData(_, url::Origin(origin1), _, _, _, _)).Times(1); |
+ EXPECT_CALL(*GetContentBrowserClient(), |
+ ClearSiteData(_, url::Origin(origin2), _, _, _, _)).Times(1); |
+ EXPECT_CALL(*GetContentBrowserClient(), |
+ ClearSiteData(_, url::Origin(origin3), _, _, _, _)).Times(0); |
+ EXPECT_CALL(*GetContentBrowserClient(), |
+ ClearSiteData(_, url::Origin(origin4), _, _, _, _)).Times(2); |
+ |
+ url = https_server()->GetURL("origin1.com", "/anything-in-workers-scope"); |
+ AddQuery(&url, "origin1", origin1.spec()); |
+ AddQuery(&url, "origin2", origin2.spec()); |
+ AddQuery(&url, "origin3", origin3.spec()); |
+ AddQuery(&url, "origin4", origin4.spec()); |
+ NavigateToURL(shell(), url); |
+ WaitForTitle(shell(), "done"); |
+} |
+ |
+// 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); |
+ |
+ const struct TestCase { |
+ bool same_origin; |
+ std::string credentials; |
+ bool should_run; |
+ } kTestCases[] = { |
+ { 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 (const TestCase& test_case : kTestCases) { |
+ 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 the script also handles fetch() error which |
+ // might be thrown 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() { document.title = 'done'; })" |
+ ".catch(function() { document.title = '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); |
+ WaitForTitle(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", "/"); |