| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/memory/ref_counted.h" |
| #include "content/browser/frame_host/ancestor_throttle.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/navigation_throttle.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "net/http/http_response_headers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using HeaderDisposition = AncestorThrottle::HeaderDisposition; |
| |
| net::HttpResponseHeaders* GetAncestorHeaders(const char* xfo, const char* csp) { |
| std::string header_string("HTTP/1.1 200 OK\nX-Frame-Options: "); |
| header_string += xfo; |
| if (csp != nullptr) { |
| header_string += "\nContent-Security-Policy: "; |
| header_string += csp; |
| } |
| header_string += "\n\n"; |
| std::replace(header_string.begin(), header_string.end(), '\n', '\0'); |
| net::HttpResponseHeaders* headers = |
| new net::HttpResponseHeaders(header_string); |
| EXPECT_TRUE(headers->HasHeader("X-Frame-Options")); |
| if (csp != nullptr) |
| EXPECT_TRUE(headers->HasHeader("Content-Security-Policy")); |
| return headers; |
| } |
| |
| } // namespace |
| |
| // AncestorThrottleTest |
| // ------------------------------------------------------------- |
| |
| class AncestorThrottleTest : public testing::Test {}; |
| |
| TEST_F(AncestorThrottleTest, ParsingXFrameOptions) { |
| struct TestCase { |
| const char* header; |
| AncestorThrottle::HeaderDisposition expected; |
| const char* value; |
| } cases[] = { |
| // Basic keywords |
| {"DENY", HeaderDisposition::DENY, "DENY"}, |
| {"SAMEORIGIN", HeaderDisposition::SAMEORIGIN, "SAMEORIGIN"}, |
| {"ALLOWALL", HeaderDisposition::ALLOWALL, "ALLOWALL"}, |
| |
| // Repeated keywords |
| {"DENY,DENY", HeaderDisposition::DENY, "DENY, DENY"}, |
| {"SAMEORIGIN,SAMEORIGIN", HeaderDisposition::SAMEORIGIN, |
| "SAMEORIGIN, SAMEORIGIN"}, |
| {"ALLOWALL,ALLOWALL", HeaderDisposition::ALLOWALL, "ALLOWALL, ALLOWALL"}, |
| |
| // Case-insensitive |
| {"deNy", HeaderDisposition::DENY, "deNy"}, |
| {"sAmEorIgIn", HeaderDisposition::SAMEORIGIN, "sAmEorIgIn"}, |
| {"AlLOWaLL", HeaderDisposition::ALLOWALL, "AlLOWaLL"}, |
| |
| // Trim whitespace |
| {" DENY", HeaderDisposition::DENY, "DENY"}, |
| {"SAMEORIGIN ", HeaderDisposition::SAMEORIGIN, "SAMEORIGIN"}, |
| {" ALLOWALL ", HeaderDisposition::ALLOWALL, "ALLOWALL"}, |
| {" DENY", HeaderDisposition::DENY, "DENY"}, |
| {"SAMEORIGIN ", HeaderDisposition::SAMEORIGIN, "SAMEORIGIN"}, |
| {" ALLOWALL ", HeaderDisposition::ALLOWALL, "ALLOWALL"}, |
| {" DENY , DENY ", HeaderDisposition::DENY, "DENY, DENY"}, |
| {"SAMEORIGIN, SAMEORIGIN", HeaderDisposition::SAMEORIGIN, |
| "SAMEORIGIN, SAMEORIGIN"}, |
| {"ALLOWALL ,ALLOWALL", HeaderDisposition::ALLOWALL, |
| "ALLOWALL, ALLOWALL"}, |
| }; |
| |
| AncestorThrottle throttle(nullptr); |
| for (const auto& test : cases) { |
| SCOPED_TRACE(test.header); |
| scoped_refptr<net::HttpResponseHeaders> headers = |
| GetAncestorHeaders(test.header, nullptr); |
| std::string header_value; |
| EXPECT_EQ(test.expected, |
| throttle.ParseHeader(headers.get(), &header_value)); |
| EXPECT_EQ(test.value, header_value); |
| } |
| } |
| |
| TEST_F(AncestorThrottleTest, ErrorsParsingXFrameOptions) { |
| struct TestCase { |
| const char* header; |
| AncestorThrottle::HeaderDisposition expected; |
| const char* failure; |
| } cases[] = { |
| // Empty == Invalid. |
| {"", HeaderDisposition::INVALID, ""}, |
| |
| // Invalid |
| {"INVALID", HeaderDisposition::INVALID, "INVALID"}, |
| {"INVALID DENY", HeaderDisposition::INVALID, "INVALID DENY"}, |
| {"DENY DENY", HeaderDisposition::INVALID, "DENY DENY"}, |
| {"DE NY", HeaderDisposition::INVALID, "DE NY"}, |
| |
| // Conflicts |
| {"INVALID,DENY", HeaderDisposition::CONFLICT, "INVALID, DENY"}, |
| {"DENY,ALLOWALL", HeaderDisposition::CONFLICT, "DENY, ALLOWALL"}, |
| {"SAMEORIGIN,DENY", HeaderDisposition::CONFLICT, "SAMEORIGIN, DENY"}, |
| {"ALLOWALL,SAMEORIGIN", HeaderDisposition::CONFLICT, |
| "ALLOWALL, SAMEORIGIN"}, |
| {"DENY, SAMEORIGIN", HeaderDisposition::CONFLICT, "DENY, SAMEORIGIN"}}; |
| |
| AncestorThrottle throttle(nullptr); |
| for (const auto& test : cases) { |
| SCOPED_TRACE(test.header); |
| scoped_refptr<net::HttpResponseHeaders> headers = |
| GetAncestorHeaders(test.header, nullptr); |
| std::string header_value; |
| EXPECT_EQ(test.expected, |
| throttle.ParseHeader(headers.get(), &header_value)); |
| EXPECT_EQ(test.failure, header_value); |
| } |
| } |
| |
| TEST_F(AncestorThrottleTest, IgnoreWhenFrameAncestorsPresent) { |
| struct TestCase { |
| const char* csp; |
| AncestorThrottle::HeaderDisposition expected; |
| } cases[] = { |
| {"", HeaderDisposition::DENY}, |
| {"frame-ancestors 'none'", HeaderDisposition::BYPASS}, |
| {"frame-ancestors *", HeaderDisposition::BYPASS}, |
| {"frame-ancestors 'self'", HeaderDisposition::BYPASS}, |
| {"frame-ancestors https://example.com", HeaderDisposition::BYPASS}, |
| {"fRaMe-AnCeStOrS *", HeaderDisposition::BYPASS}, |
| {"directive1; frame-ancestors 'none'", HeaderDisposition::BYPASS}, |
| {"directive1; frame-ancestors *", HeaderDisposition::BYPASS}, |
| {"directive1; frame-ancestors 'self'", HeaderDisposition::BYPASS}, |
| {"directive1; frame-ancestors https://example.com", |
| HeaderDisposition::BYPASS}, |
| {"directive1; fRaMe-AnCeStOrS *", HeaderDisposition::BYPASS}, |
| {"policy, frame-ancestors 'none'", HeaderDisposition::BYPASS}, |
| {"policy, frame-ancestors *", HeaderDisposition::BYPASS}, |
| {"policy, frame-ancestors 'self'", HeaderDisposition::BYPASS}, |
| {"policy, frame-ancestors https://example.com", |
| HeaderDisposition::BYPASS}, |
| {"policy, frame-ancestors 'none'", HeaderDisposition::BYPASS}, |
| {"policy, directive1; frame-ancestors *", HeaderDisposition::BYPASS}, |
| {"policy, directive1; frame-ancestors 'self'", HeaderDisposition::BYPASS}, |
| {"policy, directive1; frame-ancestors https://example.com", |
| HeaderDisposition::BYPASS}, |
| {"policy, directive1; fRaMe-AnCeStOrS *", HeaderDisposition::BYPASS}, |
| {"policy, directive1; fRaMe-AnCeStOrS *", HeaderDisposition::BYPASS}, |
| |
| {"not-frame-ancestors *", HeaderDisposition::DENY}, |
| {"frame-ancestors-are-lovely", HeaderDisposition::DENY}, |
| {"directive1; not-frame-ancestors *", HeaderDisposition::DENY}, |
| {"directive1; frame-ancestors-are-lovely", HeaderDisposition::DENY}, |
| {"policy, not-frame-ancestors *", HeaderDisposition::DENY}, |
| {"policy, frame-ancestors-are-lovely", HeaderDisposition::DENY}, |
| {"policy, directive1; not-frame-ancestors *", HeaderDisposition::DENY}, |
| {"policy, directive1; frame-ancestors-are-lovely", |
| HeaderDisposition::DENY}, |
| }; |
| |
| AncestorThrottle throttle(nullptr); |
| for (const auto& test : cases) { |
| SCOPED_TRACE(test.csp); |
| scoped_refptr<net::HttpResponseHeaders> headers = |
| GetAncestorHeaders("DENY", test.csp); |
| std::string header_value; |
| EXPECT_EQ(test.expected, |
| throttle.ParseHeader(headers.get(), &header_value)); |
| EXPECT_EQ("DENY", header_value); |
| } |
| } |
| |
| } // namespace content |