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

Unified 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 side-by-side diff with in-line comments
Download patch
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", "/");

Powered by Google App Engine
This is Rietveld 408576698