// Copyright (c) 2012 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 <windows.h> #include <atlsecurity.h> #include <shellapi.h> #include <string> #include <vector> #include "base/basictypes.h" #include "base/file_util.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_handle.h" #include "base/message_loop/message_loop.h" #include "base/path_service.h" #include "base/strings/utf_string_conversions.h" #include "net/base/net_util.h" #include "chrome/browser/automation/url_request_automation_job.h" #include "chrome/common/chrome_version_info.h" #include "chrome_frame/chrome_frame_automation.h" #include "chrome_frame/chrome_frame_delegate.h" #include "chrome_frame/html_utils.h" #include "testing/gtest/include/gtest/gtest.h" #include "webkit/common/user_agent/user_agent_util.h" const char kChromeFrameUserAgent[] = "chromeframe"; class HtmlUtilUnittest : public testing::Test { protected: // Constructor HtmlUtilUnittest() {} // Returns the test path given a test case. virtual bool GetTestPath(const std::string& test_case, base::FilePath* path) { if (!path) { NOTREACHED(); return false; } base::FilePath test_path; if (!PathService::Get(base::DIR_SOURCE_ROOT, &test_path)) { NOTREACHED(); return false; } test_path = test_path.AppendASCII("chrome_frame"); test_path = test_path.AppendASCII("test"); test_path = test_path.AppendASCII("html_util_test_data"); test_path = test_path.AppendASCII(test_case); *path = test_path; return true; } virtual bool GetTestData(const std::string& test_case, std::wstring* data) { if (!data) { NOTREACHED(); return false; } base::FilePath path; if (!GetTestPath(test_case, &path)) { NOTREACHED(); return false; } std::string raw_data; base::ReadFileToString(path, &raw_data); // Convert to wide using the "best effort" assurance described in // string_util.h data->assign(UTF8ToWide(raw_data)); return true; } }; TEST_F(HtmlUtilUnittest, BasicTest) { std::wstring test_data; GetTestData("basic_test.html", &test_data); HTMLScanner scanner(test_data.c_str()); // Grab the meta tag from the document and ensure that we get exactly one. HTMLScanner::StringRangeList tag_list; scanner.GetTagsByName(L"meta", &tag_list, L"body"); ASSERT_EQ(1, tag_list.size()); // Pull out the http-equiv attribute and check its value: HTMLScanner::StringRange attribute_value; EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value)); EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible")); // Pull out the content attribute and check its value: EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value)); EXPECT_TRUE(attribute_value.Equals(L"chrome=1")); } TEST_F(HtmlUtilUnittest, QuotesTest) { std::wstring test_data; GetTestData("quotes_test.html", &test_data); HTMLScanner scanner(test_data.c_str()); // Grab the meta tag from the document and ensure that we get exactly one. HTMLScanner::StringRangeList tag_list; scanner.GetTagsByName(L"meta", &tag_list, L"body"); ASSERT_EQ(1, tag_list.size()); // Pull out the http-equiv attribute and check its value: HTMLScanner::StringRange attribute_value; EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value)); EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible")); // Pull out the content attribute and check its value: EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value)); EXPECT_TRUE(attribute_value.Equals(L"chrome=1")); } TEST_F(HtmlUtilUnittest, DegenerateCasesTest) { std::wstring test_data; GetTestData("degenerate_cases_test.html", &test_data); HTMLScanner scanner(test_data.c_str()); // Scan for meta tags in the document. We expect not to pick up the one // that appears to be there since it is technically inside a quote block. HTMLScanner::StringRangeList tag_list; scanner.GetTagsByName(L"meta", &tag_list, L"body"); EXPECT_TRUE(tag_list.empty()); } TEST_F(HtmlUtilUnittest, MultipleTagsTest) { std::wstring test_data; GetTestData("multiple_tags.html", &test_data); HTMLScanner scanner(test_data.c_str()); // Grab the meta tag from the document and ensure that we get exactly three. HTMLScanner::StringRangeList tag_list; scanner.GetTagsByName(L"meta", &tag_list, L"body"); EXPECT_EQ(7, tag_list.size()); // Pull out the content attribute for each tag and check its value: HTMLScanner::StringRange attribute_value; HTMLScanner::StringRangeList::const_iterator tag_list_iter( tag_list.begin()); int valid_tag_count = 0; for (; tag_list_iter != tag_list.end(); tag_list_iter++) { HTMLScanner::StringRange attribute_value; if (tag_list_iter->GetTagAttribute(L"http-equiv", &attribute_value) && attribute_value.Equals(L"X-UA-Compatible")) { EXPECT_TRUE(tag_list_iter->GetTagAttribute(L"content", &attribute_value)); EXPECT_TRUE(attribute_value.Equals(L"chrome=1")); valid_tag_count++; } } EXPECT_EQ(3, valid_tag_count); } TEST_F(HtmlUtilUnittest, ShortDegenerateTest1) { std::wstring test_data( L"<foo><META http-equiv=X-UA-Compatible content='chrome=1'"); HTMLScanner scanner(test_data.c_str()); // Scan for meta tags in the document. We expect not to pick up the one // that is there since it is not properly closed. HTMLScanner::StringRangeList tag_list; scanner.GetTagsByName(L"meta", &tag_list, L"body"); EXPECT_TRUE(tag_list.empty()); } TEST_F(HtmlUtilUnittest, ShortDegenerateTest2) { std::wstring test_data( L"<foo <META http-equiv=X-UA-Compatible content='chrome=1'/>"); HTMLScanner scanner(test_data.c_str()); // Scan for meta tags in the document. We expect not to pick up the one // that appears to be there since it is inside a non-closed tag. HTMLScanner::StringRangeList tag_list; scanner.GetTagsByName(L"meta", &tag_list, L"body"); EXPECT_TRUE(tag_list.empty()); } TEST_F(HtmlUtilUnittest, QuoteInsideHTMLCommentTest) { std::wstring test_data( L"<!-- comment' --><META http-equiv=X-UA-Compatible content='chrome=1'/>"); HTMLScanner scanner(test_data.c_str()); // Grab the meta tag from the document and ensure that we get exactly one. HTMLScanner::StringRangeList tag_list; scanner.GetTagsByName(L"meta", &tag_list, L"body"); ASSERT_EQ(1, tag_list.size()); // Pull out the http-equiv attribute and check its value: HTMLScanner::StringRange attribute_value; EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value)); EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible")); // Pull out the content attribute and check its value: EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value)); EXPECT_TRUE(attribute_value.Equals(L"chrome=1")); } TEST_F(HtmlUtilUnittest, CloseTagInsideHTMLCommentTest) { std::wstring test_data( L"<!-- comment> <META http-equiv=X-UA-Compatible content='chrome=1'/>-->"); HTMLScanner scanner(test_data.c_str()); // Ensure that the the meta tag is NOT detected. HTMLScanner::StringRangeList tag_list; scanner.GetTagsByName(L"meta", &tag_list, L"body"); ASSERT_TRUE(tag_list.empty()); } TEST_F(HtmlUtilUnittest, IEConditionalCommentTest) { std::wstring test_data( L"<!--[if lte IE 8]><META http-equiv=X-UA-Compatible content='chrome=1'/>" L"<![endif]-->"); HTMLScanner scanner(test_data.c_str()); // Ensure that the the meta tag IS detected. HTMLScanner::StringRangeList tag_list; scanner.GetTagsByName(L"meta", &tag_list, L"body"); ASSERT_EQ(1, tag_list.size()); } TEST_F(HtmlUtilUnittest, IEConditionalCommentWithNestedCommentTest) { std::wstring test_data( L"<!--[if IE]><!--<META http-equiv=X-UA-Compatible content='chrome=1'/>" L"--><![endif]-->"); HTMLScanner scanner(test_data.c_str()); // Ensure that the the meta tag IS NOT detected. HTMLScanner::StringRangeList tag_list; scanner.GetTagsByName(L"meta", &tag_list, L"body"); ASSERT_TRUE(tag_list.empty()); } TEST_F(HtmlUtilUnittest, IEConditionalCommentWithMultipleNestedTagsTest) { std::wstring test_data( L"<!--[if lte IE 8]> <META http-equiv=X-UA-Compatible " L"content='chrome=1'/><foo bar></foo><foo baz/><![endif]-->" L"<boo hoo><boo hah>"); HTMLScanner scanner(test_data.c_str()); // Ensure that the the meta tag IS detected. HTMLScanner::StringRangeList meta_tag_list; scanner.GetTagsByName(L"meta", &meta_tag_list, L"body"); ASSERT_EQ(1, meta_tag_list.size()); // Ensure that the foo tags are also detected. HTMLScanner::StringRangeList foo_tag_list; scanner.GetTagsByName(L"foo", &foo_tag_list, L"body"); ASSERT_EQ(2, foo_tag_list.size()); // Ensure that the boo tags are also detected. HTMLScanner::StringRangeList boo_tag_list; scanner.GetTagsByName(L"boo", &boo_tag_list, L"body"); ASSERT_EQ(2, boo_tag_list.size()); } TEST_F(HtmlUtilUnittest, IEConditionalCommentWithAlternateEndingTest) { std::wstring test_data( L"<!--[if lte IE 8]> <META http-equiv=X-UA-Compatible " L"content='chrome=1'/><foo bar></foo><foo baz/><![endif]>" L"<boo hoo><!--><boo hah>"); HTMLScanner scanner(test_data.c_str()); // Ensure that the the meta tag IS detected. HTMLScanner::StringRangeList meta_tag_list; scanner.GetTagsByName(L"meta", &meta_tag_list, L"body"); ASSERT_EQ(1, meta_tag_list.size()); // Ensure that the foo tags are also detected. HTMLScanner::StringRangeList foo_tag_list; scanner.GetTagsByName(L"foo", &foo_tag_list, L"body"); ASSERT_EQ(2, foo_tag_list.size()); // Ensure that the boo tags are also detected. HTMLScanner::StringRangeList boo_tag_list; scanner.GetTagsByName(L"boo", &boo_tag_list, L"body"); ASSERT_EQ(2, boo_tag_list.size()); } TEST_F(HtmlUtilUnittest, IEConditionalCommentNonTerminatedTest) { // This test shouldn't detect any tags up until the end of the conditional // comment tag. std::wstring test_data( L"<!--[if lte IE 8> <META http-equiv=X-UA-Compatible " L"content='chrome=1'/><foo bar></foo><foo baz/><![endif]>" L"<boo hoo><!--><boo hah>"); HTMLScanner scanner(test_data.c_str()); // Ensure that the the meta tag IS NOT detected. HTMLScanner::StringRangeList meta_tag_list; scanner.GetTagsByName(L"meta", &meta_tag_list, L"body"); ASSERT_TRUE(meta_tag_list.empty()); // Ensure that the foo tags are NOT detected. HTMLScanner::StringRangeList foo_tag_list; scanner.GetTagsByName(L"foo", &foo_tag_list, L"body"); ASSERT_TRUE(foo_tag_list.empty()); // Ensure that the boo tags are detected. HTMLScanner::StringRangeList boo_tag_list; scanner.GetTagsByName(L"boo", &boo_tag_list, L"body"); ASSERT_EQ(2, boo_tag_list.size()); } struct UserAgentTestCase { std::string input_; std::string expected_; } user_agent_test_cases[] = { { "", "" }, { "Mozilla/4.7 [en] (WinNT; U)", "Mozilla/4.7 [en] (WinNT; U; chromeframe/0.0.0.0)" }, { "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT)", "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT; chromeframe/0.0.0.0)" }, { "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; T312461; " ".NET CLR 1.1.4322)", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; T312461; " ".NET CLR 1.1.4322; chromeframe/0.0.0.0)" }, { "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 4.0) Opera 5.11 [en]", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 4.0; chromeframe/0.0.0.0) " "Opera 5.11 [en]" }, { "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; " "chromeframe/0.0.0.0)" }, { "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.0.2) " "Gecko/20030208 Netscape/7.02", "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.0.2; " "chromeframe/0.0.0.0) Gecko/20030208 Netscape/7.02" }, { "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.6) Gecko/20040612 " "Firefox/0.8", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.6; chromeframe/0.0.0.0) " "Gecko/20040612 Firefox/0.8" }, { "Mozilla/5.0 (compatible; Konqueror/3.2; Linux) (KHTML, like Gecko)", "Mozilla/5.0 (compatible; Konqueror/3.2; Linux; chromeframe/0.0.0.0) " "(KHTML, like Gecko)" }, { "Lynx/2.8.4rel.1 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.6h", "Lynx/2.8.4rel.1 libwww-FM/2.14 SSL-MM/1.4.1 " "OpenSSL/0.9.6h chromeframe/0.0.0.0", }, { "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.7.10) " "Gecko/20050716 Firefox/1.0.6", "Mozilla/5.0 (X11; U; Linux i686 (x86_64; chromeframe/0.0.0.0); en-US; " "rv:1.7.10) Gecko/20050716 Firefox/1.0.6" }, { "Invalid/1.1 ((((((", "Invalid/1.1 (((((( chromeframe/0.0.0.0", }, { "Invalid/1.1 ()))))", "Invalid/1.1 ( chromeframe/0.0.0.0)))))", }, { "Strange/1.1 ()", "Strange/1.1 ( chromeframe/0.0.0.0)", } }; TEST_F(HtmlUtilUnittest, AddChromeFrameToUserAgentValue) { for (int i = 0; i < arraysize(user_agent_test_cases); ++i) { std::string new_ua( http_utils::AddChromeFrameToUserAgentValue( user_agent_test_cases[i].input_)); EXPECT_EQ(user_agent_test_cases[i].expected_, new_ua); } // Now do the same test again, but test that we don't add the chromeframe // tag if we've already added it. for (int i = 0; i < arraysize(user_agent_test_cases); ++i) { std::string ua(user_agent_test_cases[i].expected_); std::string new_ua(http_utils::AddChromeFrameToUserAgentValue(ua)); EXPECT_EQ(user_agent_test_cases[i].expected_, new_ua); } } TEST_F(HtmlUtilUnittest, RemoveChromeFrameFromUserAgentValue) { for (int i = 0; i < arraysize(user_agent_test_cases); ++i) { std::string new_ua( http_utils::RemoveChromeFrameFromUserAgentValue( user_agent_test_cases[i].expected_)); EXPECT_EQ(user_agent_test_cases[i].input_, new_ua); } // Also test that we don't modify the UA if chromeframe is not present. for (int i = 0; i < arraysize(user_agent_test_cases); ++i) { std::string ua(user_agent_test_cases[i].input_); std::string new_ua(http_utils::RemoveChromeFrameFromUserAgentValue(ua)); EXPECT_EQ(user_agent_test_cases[i].input_, new_ua); } } TEST_F(HtmlUtilUnittest, GetDefaultUserAgentHeaderWithCFTag) { std::string ua(http_utils::GetDefaultUserAgentHeaderWithCFTag()); EXPECT_NE(0u, ua.length()); EXPECT_NE(std::string::npos, ua.find("Mozilla")); EXPECT_NE(std::string::npos, ua.find(kChromeFrameUserAgent)); } TEST_F(HtmlUtilUnittest, GetChromeUserAgent) { // This code is duplicated from chrome_content_client.cc to avoid // introducing a link-time dependency on chrome_common. chrome::VersionInfo version_info; std::string product("Chrome/"); product += version_info.is_valid() ? version_info.Version() : "0.0.0.0"; std::string chrome_ua(webkit_glue::BuildUserAgentFromProduct(product)); const char* ua = http_utils::GetChromeUserAgent(); EXPECT_EQ(ua, chrome_ua); } TEST_F(HtmlUtilUnittest, GetDefaultUserAgent) { std::string ua(http_utils::GetDefaultUserAgent()); EXPECT_NE(0u, ua.length()); EXPECT_NE(std::string::npos, ua.find("Mozilla")); } TEST_F(HtmlUtilUnittest, GetChromeFrameUserAgent) { const char* call1 = http_utils::GetChromeFrameUserAgent(); const char* call2 = http_utils::GetChromeFrameUserAgent(); // Expect static buffer since caller does no cleanup. EXPECT_EQ(call1, call2); std::string ua(call1); EXPECT_EQ("chromeframe/0.0.0.0", ua); } TEST(HttpUtils, HasFrameBustingHeader) { // Simple negative cases. EXPECT_FALSE(http_utils::HasFrameBustingHeader("")); EXPECT_FALSE(http_utils::HasFrameBustingHeader("Content-Type: text/plain")); EXPECT_FALSE(http_utils::HasFrameBustingHeader("X-Frame-Optionss: ALLOWALL")); // Explicit negative cases, test that we ignore case. EXPECT_FALSE(http_utils::HasFrameBustingHeader("X-Frame-Options: ALLOWALL")); EXPECT_FALSE(http_utils::HasFrameBustingHeader("X-Frame-Options: allowall")); EXPECT_FALSE(http_utils::HasFrameBustingHeader("X-Frame-Options: ALLowalL")); // Added space, ensure stripped out EXPECT_FALSE(http_utils::HasFrameBustingHeader( "X-Frame-Options: ALLOWALL ")); // Added space with linefeed, ensure still stripped out EXPECT_FALSE(http_utils::HasFrameBustingHeader( "X-Frame-Options: ALLOWALL \r\n")); // Multiple identical headers, all of them allowing framing. EXPECT_FALSE(http_utils::HasFrameBustingHeader( "X-Frame-Options: ALLOWALL\r\n" "X-Frame-Options: ALLOWALL\r\n" "X-Frame-Options: ALLOWALL")); // Interleave with other headers. EXPECT_FALSE(http_utils::HasFrameBustingHeader( "Content-Type: text/plain\r\n" "X-Frame-Options: ALLOWALL\r\n" "Content-Length: 42")); // Simple positive cases. EXPECT_TRUE(http_utils::HasFrameBustingHeader("X-Frame-Options: deny")); EXPECT_TRUE(http_utils::HasFrameBustingHeader( "X-Frame-Options: SAMEorigin")); // Verify that we pick up case changes in the header name too: EXPECT_TRUE(http_utils::HasFrameBustingHeader("X-FRAME-OPTIONS: deny")); EXPECT_TRUE(http_utils::HasFrameBustingHeader("x-frame-options: deny")); EXPECT_TRUE(http_utils::HasFrameBustingHeader("X-frame-optionS: deny")); EXPECT_TRUE(http_utils::HasFrameBustingHeader("X-Frame-optionS: deny")); // Allowall entries do not override the denying entries, are // order-independent, and the deny entries can interleave with // other headers. EXPECT_TRUE(http_utils::HasFrameBustingHeader( "Content-Length: 42\r\n" "X-Frame-Options: ALLOWall\r\n" "X-Frame-Options: deny\r\n")); EXPECT_TRUE(http_utils::HasFrameBustingHeader( "X-Frame-Options: ALLOWall\r\n" "Content-Length: 42\r\n" "X-Frame-Options: SAMEORIGIN\r\n")); EXPECT_TRUE(http_utils::HasFrameBustingHeader( "X-Frame-Options: deny\r\n" "X-Frame-Options: ALLOWall\r\n" "Content-Length: 42\r\n")); EXPECT_TRUE(http_utils::HasFrameBustingHeader( "X-Frame-Options: SAMEORIGIN\r\n" "X-Frame-Options: ALLOWall\r\n")); }