1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <windows.h> 6 #include <atlsecurity.h> 7 #include <shellapi.h> 8 #include <string> 9 #include <vector> 10 11 #include "base/basictypes.h" 12 #include "base/file_util.h" 13 #include "base/memory/ref_counted.h" 14 #include "base/memory/scoped_handle.h" 15 #include "base/message_loop/message_loop.h" 16 #include "base/path_service.h" 17 #include "base/strings/utf_string_conversions.h" 18 #include "net/base/net_util.h" 19 20 #include "chrome/browser/automation/url_request_automation_job.h" 21 #include "chrome/common/chrome_version_info.h" 22 #include "chrome_frame/chrome_frame_automation.h" 23 #include "chrome_frame/chrome_frame_delegate.h" 24 #include "chrome_frame/html_utils.h" 25 #include "testing/gtest/include/gtest/gtest.h" 26 #include "webkit/common/user_agent/user_agent_util.h" 27 28 const char kChromeFrameUserAgent[] = "chromeframe"; 29 30 class HtmlUtilUnittest : public testing::Test { 31 protected: 32 // Constructor 33 HtmlUtilUnittest() {} 34 35 // Returns the test path given a test case. 36 virtual bool GetTestPath(const std::string& test_case, base::FilePath* path) { 37 if (!path) { 38 NOTREACHED(); 39 return false; 40 } 41 42 base::FilePath test_path; 43 if (!PathService::Get(base::DIR_SOURCE_ROOT, &test_path)) { 44 NOTREACHED(); 45 return false; 46 } 47 48 test_path = test_path.AppendASCII("chrome_frame"); 49 test_path = test_path.AppendASCII("test"); 50 test_path = test_path.AppendASCII("html_util_test_data"); 51 test_path = test_path.AppendASCII(test_case); 52 53 *path = test_path; 54 return true; 55 } 56 57 virtual bool GetTestData(const std::string& test_case, std::wstring* data) { 58 if (!data) { 59 NOTREACHED(); 60 return false; 61 } 62 63 base::FilePath path; 64 if (!GetTestPath(test_case, &path)) { 65 NOTREACHED(); 66 return false; 67 } 68 69 std::string raw_data; 70 base::ReadFileToString(path, &raw_data); 71 72 // Convert to wide using the "best effort" assurance described in 73 // string_util.h 74 data->assign(UTF8ToWide(raw_data)); 75 return true; 76 } 77 }; 78 79 TEST_F(HtmlUtilUnittest, BasicTest) { 80 std::wstring test_data; 81 GetTestData("basic_test.html", &test_data); 82 83 HTMLScanner scanner(test_data.c_str()); 84 85 // Grab the meta tag from the document and ensure that we get exactly one. 86 HTMLScanner::StringRangeList tag_list; 87 scanner.GetTagsByName(L"meta", &tag_list, L"body"); 88 ASSERT_EQ(1, tag_list.size()); 89 90 // Pull out the http-equiv attribute and check its value: 91 HTMLScanner::StringRange attribute_value; 92 EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value)); 93 EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible")); 94 95 // Pull out the content attribute and check its value: 96 EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value)); 97 EXPECT_TRUE(attribute_value.Equals(L"chrome=1")); 98 } 99 100 TEST_F(HtmlUtilUnittest, QuotesTest) { 101 std::wstring test_data; 102 GetTestData("quotes_test.html", &test_data); 103 104 HTMLScanner scanner(test_data.c_str()); 105 106 // Grab the meta tag from the document and ensure that we get exactly one. 107 HTMLScanner::StringRangeList tag_list; 108 scanner.GetTagsByName(L"meta", &tag_list, L"body"); 109 ASSERT_EQ(1, tag_list.size()); 110 111 // Pull out the http-equiv attribute and check its value: 112 HTMLScanner::StringRange attribute_value; 113 EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value)); 114 EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible")); 115 116 // Pull out the content attribute and check its value: 117 EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value)); 118 EXPECT_TRUE(attribute_value.Equals(L"chrome=1")); 119 } 120 121 TEST_F(HtmlUtilUnittest, DegenerateCasesTest) { 122 std::wstring test_data; 123 GetTestData("degenerate_cases_test.html", &test_data); 124 125 HTMLScanner scanner(test_data.c_str()); 126 127 // Scan for meta tags in the document. We expect not to pick up the one 128 // that appears to be there since it is technically inside a quote block. 129 HTMLScanner::StringRangeList tag_list; 130 scanner.GetTagsByName(L"meta", &tag_list, L"body"); 131 EXPECT_TRUE(tag_list.empty()); 132 } 133 134 TEST_F(HtmlUtilUnittest, MultipleTagsTest) { 135 std::wstring test_data; 136 GetTestData("multiple_tags.html", &test_data); 137 138 HTMLScanner scanner(test_data.c_str()); 139 140 // Grab the meta tag from the document and ensure that we get exactly three. 141 HTMLScanner::StringRangeList tag_list; 142 scanner.GetTagsByName(L"meta", &tag_list, L"body"); 143 EXPECT_EQ(7, tag_list.size()); 144 145 // Pull out the content attribute for each tag and check its value: 146 HTMLScanner::StringRange attribute_value; 147 HTMLScanner::StringRangeList::const_iterator tag_list_iter( 148 tag_list.begin()); 149 int valid_tag_count = 0; 150 for (; tag_list_iter != tag_list.end(); tag_list_iter++) { 151 HTMLScanner::StringRange attribute_value; 152 if (tag_list_iter->GetTagAttribute(L"http-equiv", &attribute_value) && 153 attribute_value.Equals(L"X-UA-Compatible")) { 154 EXPECT_TRUE(tag_list_iter->GetTagAttribute(L"content", &attribute_value)); 155 EXPECT_TRUE(attribute_value.Equals(L"chrome=1")); 156 valid_tag_count++; 157 } 158 } 159 EXPECT_EQ(3, valid_tag_count); 160 } 161 162 TEST_F(HtmlUtilUnittest, ShortDegenerateTest1) { 163 std::wstring test_data( 164 L"<foo><META http-equiv=X-UA-Compatible content='chrome=1'"); 165 166 HTMLScanner scanner(test_data.c_str()); 167 168 // Scan for meta tags in the document. We expect not to pick up the one 169 // that is there since it is not properly closed. 170 HTMLScanner::StringRangeList tag_list; 171 scanner.GetTagsByName(L"meta", &tag_list, L"body"); 172 EXPECT_TRUE(tag_list.empty()); 173 } 174 175 TEST_F(HtmlUtilUnittest, ShortDegenerateTest2) { 176 std::wstring test_data( 177 L"<foo <META http-equiv=X-UA-Compatible content='chrome=1'/>"); 178 179 HTMLScanner scanner(test_data.c_str()); 180 181 // Scan for meta tags in the document. We expect not to pick up the one 182 // that appears to be there since it is inside a non-closed tag. 183 HTMLScanner::StringRangeList tag_list; 184 scanner.GetTagsByName(L"meta", &tag_list, L"body"); 185 EXPECT_TRUE(tag_list.empty()); 186 } 187 188 TEST_F(HtmlUtilUnittest, QuoteInsideHTMLCommentTest) { 189 std::wstring test_data( 190 L"<!-- comment' --><META http-equiv=X-UA-Compatible content='chrome=1'/>"); 191 192 HTMLScanner scanner(test_data.c_str()); 193 194 // Grab the meta tag from the document and ensure that we get exactly one. 195 HTMLScanner::StringRangeList tag_list; 196 scanner.GetTagsByName(L"meta", &tag_list, L"body"); 197 ASSERT_EQ(1, tag_list.size()); 198 199 // Pull out the http-equiv attribute and check its value: 200 HTMLScanner::StringRange attribute_value; 201 EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value)); 202 EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible")); 203 204 // Pull out the content attribute and check its value: 205 EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value)); 206 EXPECT_TRUE(attribute_value.Equals(L"chrome=1")); 207 } 208 209 TEST_F(HtmlUtilUnittest, CloseTagInsideHTMLCommentTest) { 210 std::wstring test_data( 211 L"<!-- comment> <META http-equiv=X-UA-Compatible content='chrome=1'/>-->"); 212 213 HTMLScanner scanner(test_data.c_str()); 214 215 // Ensure that the the meta tag is NOT detected. 216 HTMLScanner::StringRangeList tag_list; 217 scanner.GetTagsByName(L"meta", &tag_list, L"body"); 218 ASSERT_TRUE(tag_list.empty()); 219 } 220 221 TEST_F(HtmlUtilUnittest, IEConditionalCommentTest) { 222 std::wstring test_data( 223 L"<!--[if lte IE 8]><META http-equiv=X-UA-Compatible content='chrome=1'/>" 224 L"<![endif]-->"); 225 226 HTMLScanner scanner(test_data.c_str()); 227 228 // Ensure that the the meta tag IS detected. 229 HTMLScanner::StringRangeList tag_list; 230 scanner.GetTagsByName(L"meta", &tag_list, L"body"); 231 ASSERT_EQ(1, tag_list.size()); 232 } 233 234 TEST_F(HtmlUtilUnittest, IEConditionalCommentWithNestedCommentTest) { 235 std::wstring test_data( 236 L"<!--[if IE]><!--<META http-equiv=X-UA-Compatible content='chrome=1'/>" 237 L"--><![endif]-->"); 238 239 HTMLScanner scanner(test_data.c_str()); 240 241 // Ensure that the the meta tag IS NOT detected. 242 HTMLScanner::StringRangeList tag_list; 243 scanner.GetTagsByName(L"meta", &tag_list, L"body"); 244 ASSERT_TRUE(tag_list.empty()); 245 } 246 247 TEST_F(HtmlUtilUnittest, IEConditionalCommentWithMultipleNestedTagsTest) { 248 std::wstring test_data( 249 L"<!--[if lte IE 8]> <META http-equiv=X-UA-Compatible " 250 L"content='chrome=1'/><foo bar></foo><foo baz/><![endif]-->" 251 L"<boo hoo><boo hah>"); 252 253 HTMLScanner scanner(test_data.c_str()); 254 255 // Ensure that the the meta tag IS detected. 256 HTMLScanner::StringRangeList meta_tag_list; 257 scanner.GetTagsByName(L"meta", &meta_tag_list, L"body"); 258 ASSERT_EQ(1, meta_tag_list.size()); 259 260 // Ensure that the foo tags are also detected. 261 HTMLScanner::StringRangeList foo_tag_list; 262 scanner.GetTagsByName(L"foo", &foo_tag_list, L"body"); 263 ASSERT_EQ(2, foo_tag_list.size()); 264 265 // Ensure that the boo tags are also detected. 266 HTMLScanner::StringRangeList boo_tag_list; 267 scanner.GetTagsByName(L"boo", &boo_tag_list, L"body"); 268 ASSERT_EQ(2, boo_tag_list.size()); 269 } 270 271 TEST_F(HtmlUtilUnittest, IEConditionalCommentWithAlternateEndingTest) { 272 std::wstring test_data( 273 L"<!--[if lte IE 8]> <META http-equiv=X-UA-Compatible " 274 L"content='chrome=1'/><foo bar></foo><foo baz/><![endif]>" 275 L"<boo hoo><!--><boo hah>"); 276 277 HTMLScanner scanner(test_data.c_str()); 278 279 // Ensure that the the meta tag IS detected. 280 HTMLScanner::StringRangeList meta_tag_list; 281 scanner.GetTagsByName(L"meta", &meta_tag_list, L"body"); 282 ASSERT_EQ(1, meta_tag_list.size()); 283 284 // Ensure that the foo tags are also detected. 285 HTMLScanner::StringRangeList foo_tag_list; 286 scanner.GetTagsByName(L"foo", &foo_tag_list, L"body"); 287 ASSERT_EQ(2, foo_tag_list.size()); 288 289 // Ensure that the boo tags are also detected. 290 HTMLScanner::StringRangeList boo_tag_list; 291 scanner.GetTagsByName(L"boo", &boo_tag_list, L"body"); 292 ASSERT_EQ(2, boo_tag_list.size()); 293 } 294 295 TEST_F(HtmlUtilUnittest, IEConditionalCommentNonTerminatedTest) { 296 // This test shouldn't detect any tags up until the end of the conditional 297 // comment tag. 298 std::wstring test_data( 299 L"<!--[if lte IE 8> <META http-equiv=X-UA-Compatible " 300 L"content='chrome=1'/><foo bar></foo><foo baz/><![endif]>" 301 L"<boo hoo><!--><boo hah>"); 302 303 HTMLScanner scanner(test_data.c_str()); 304 305 // Ensure that the the meta tag IS NOT detected. 306 HTMLScanner::StringRangeList meta_tag_list; 307 scanner.GetTagsByName(L"meta", &meta_tag_list, L"body"); 308 ASSERT_TRUE(meta_tag_list.empty()); 309 310 // Ensure that the foo tags are NOT detected. 311 HTMLScanner::StringRangeList foo_tag_list; 312 scanner.GetTagsByName(L"foo", &foo_tag_list, L"body"); 313 ASSERT_TRUE(foo_tag_list.empty()); 314 315 // Ensure that the boo tags are detected. 316 HTMLScanner::StringRangeList boo_tag_list; 317 scanner.GetTagsByName(L"boo", &boo_tag_list, L"body"); 318 ASSERT_EQ(2, boo_tag_list.size()); 319 } 320 321 struct UserAgentTestCase { 322 std::string input_; 323 std::string expected_; 324 } user_agent_test_cases[] = { 325 { 326 "", "" 327 }, { 328 "Mozilla/4.7 [en] (WinNT; U)", 329 "Mozilla/4.7 [en] (WinNT; U; chromeframe/0.0.0.0)" 330 }, { 331 "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT)", 332 "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT; chromeframe/0.0.0.0)" 333 }, { 334 "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; T312461; " 335 ".NET CLR 1.1.4322)", 336 "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; T312461; " 337 ".NET CLR 1.1.4322; chromeframe/0.0.0.0)" 338 }, { 339 "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 4.0) Opera 5.11 [en]", 340 "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 4.0; chromeframe/0.0.0.0) " 341 "Opera 5.11 [en]" 342 }, { 343 "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)", 344 "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; " 345 "chromeframe/0.0.0.0)" 346 }, { 347 "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.0.2) " 348 "Gecko/20030208 Netscape/7.02", 349 "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.0.2; " 350 "chromeframe/0.0.0.0) Gecko/20030208 Netscape/7.02" 351 }, { 352 "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.6) Gecko/20040612 " 353 "Firefox/0.8", 354 "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.6; chromeframe/0.0.0.0) " 355 "Gecko/20040612 Firefox/0.8" 356 }, { 357 "Mozilla/5.0 (compatible; Konqueror/3.2; Linux) (KHTML, like Gecko)", 358 "Mozilla/5.0 (compatible; Konqueror/3.2; Linux; chromeframe/0.0.0.0) " 359 "(KHTML, like Gecko)" 360 }, { 361 "Lynx/2.8.4rel.1 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.6h", 362 "Lynx/2.8.4rel.1 libwww-FM/2.14 SSL-MM/1.4.1 " 363 "OpenSSL/0.9.6h chromeframe/0.0.0.0", 364 }, { 365 "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.7.10) " 366 "Gecko/20050716 Firefox/1.0.6", 367 "Mozilla/5.0 (X11; U; Linux i686 (x86_64; chromeframe/0.0.0.0); en-US; " 368 "rv:1.7.10) Gecko/20050716 Firefox/1.0.6" 369 }, { 370 "Invalid/1.1 ((((((", 371 "Invalid/1.1 (((((( chromeframe/0.0.0.0", 372 }, { 373 "Invalid/1.1 ()))))", 374 "Invalid/1.1 ( chromeframe/0.0.0.0)))))", 375 }, { 376 "Strange/1.1 ()", 377 "Strange/1.1 ( chromeframe/0.0.0.0)", 378 } 379 }; 380 381 TEST_F(HtmlUtilUnittest, AddChromeFrameToUserAgentValue) { 382 for (int i = 0; i < arraysize(user_agent_test_cases); ++i) { 383 std::string new_ua( 384 http_utils::AddChromeFrameToUserAgentValue( 385 user_agent_test_cases[i].input_)); 386 EXPECT_EQ(user_agent_test_cases[i].expected_, new_ua); 387 } 388 389 // Now do the same test again, but test that we don't add the chromeframe 390 // tag if we've already added it. 391 for (int i = 0; i < arraysize(user_agent_test_cases); ++i) { 392 std::string ua(user_agent_test_cases[i].expected_); 393 std::string new_ua(http_utils::AddChromeFrameToUserAgentValue(ua)); 394 EXPECT_EQ(user_agent_test_cases[i].expected_, new_ua); 395 } 396 } 397 398 TEST_F(HtmlUtilUnittest, RemoveChromeFrameFromUserAgentValue) { 399 for (int i = 0; i < arraysize(user_agent_test_cases); ++i) { 400 std::string new_ua( 401 http_utils::RemoveChromeFrameFromUserAgentValue( 402 user_agent_test_cases[i].expected_)); 403 EXPECT_EQ(user_agent_test_cases[i].input_, new_ua); 404 } 405 406 // Also test that we don't modify the UA if chromeframe is not present. 407 for (int i = 0; i < arraysize(user_agent_test_cases); ++i) { 408 std::string ua(user_agent_test_cases[i].input_); 409 std::string new_ua(http_utils::RemoveChromeFrameFromUserAgentValue(ua)); 410 EXPECT_EQ(user_agent_test_cases[i].input_, new_ua); 411 } 412 } 413 414 TEST_F(HtmlUtilUnittest, GetDefaultUserAgentHeaderWithCFTag) { 415 std::string ua(http_utils::GetDefaultUserAgentHeaderWithCFTag()); 416 EXPECT_NE(0u, ua.length()); 417 EXPECT_NE(std::string::npos, ua.find("Mozilla")); 418 EXPECT_NE(std::string::npos, ua.find(kChromeFrameUserAgent)); 419 } 420 421 TEST_F(HtmlUtilUnittest, GetChromeUserAgent) { 422 // This code is duplicated from chrome_content_client.cc to avoid 423 // introducing a link-time dependency on chrome_common. 424 chrome::VersionInfo version_info; 425 std::string product("Chrome/"); 426 product += version_info.is_valid() ? version_info.Version() : "0.0.0.0"; 427 std::string chrome_ua(webkit_glue::BuildUserAgentFromProduct(product)); 428 429 const char* ua = http_utils::GetChromeUserAgent(); 430 EXPECT_EQ(ua, chrome_ua); 431 } 432 433 TEST_F(HtmlUtilUnittest, GetDefaultUserAgent) { 434 std::string ua(http_utils::GetDefaultUserAgent()); 435 EXPECT_NE(0u, ua.length()); 436 EXPECT_NE(std::string::npos, ua.find("Mozilla")); 437 } 438 439 TEST_F(HtmlUtilUnittest, GetChromeFrameUserAgent) { 440 const char* call1 = http_utils::GetChromeFrameUserAgent(); 441 const char* call2 = http_utils::GetChromeFrameUserAgent(); 442 // Expect static buffer since caller does no cleanup. 443 EXPECT_EQ(call1, call2); 444 std::string ua(call1); 445 EXPECT_EQ("chromeframe/0.0.0.0", ua); 446 } 447 448 TEST(HttpUtils, HasFrameBustingHeader) { 449 // Simple negative cases. 450 EXPECT_FALSE(http_utils::HasFrameBustingHeader("")); 451 EXPECT_FALSE(http_utils::HasFrameBustingHeader("Content-Type: text/plain")); 452 EXPECT_FALSE(http_utils::HasFrameBustingHeader("X-Frame-Optionss: ALLOWALL")); 453 // Explicit negative cases, test that we ignore case. 454 EXPECT_FALSE(http_utils::HasFrameBustingHeader("X-Frame-Options: ALLOWALL")); 455 EXPECT_FALSE(http_utils::HasFrameBustingHeader("X-Frame-Options: allowall")); 456 EXPECT_FALSE(http_utils::HasFrameBustingHeader("X-Frame-Options: ALLowalL")); 457 // Added space, ensure stripped out 458 EXPECT_FALSE(http_utils::HasFrameBustingHeader( 459 "X-Frame-Options: ALLOWALL ")); 460 // Added space with linefeed, ensure still stripped out 461 EXPECT_FALSE(http_utils::HasFrameBustingHeader( 462 "X-Frame-Options: ALLOWALL \r\n")); 463 // Multiple identical headers, all of them allowing framing. 464 EXPECT_FALSE(http_utils::HasFrameBustingHeader( 465 "X-Frame-Options: ALLOWALL\r\n" 466 "X-Frame-Options: ALLOWALL\r\n" 467 "X-Frame-Options: ALLOWALL")); 468 // Interleave with other headers. 469 EXPECT_FALSE(http_utils::HasFrameBustingHeader( 470 "Content-Type: text/plain\r\n" 471 "X-Frame-Options: ALLOWALL\r\n" 472 "Content-Length: 42")); 473 474 // Simple positive cases. 475 EXPECT_TRUE(http_utils::HasFrameBustingHeader("X-Frame-Options: deny")); 476 EXPECT_TRUE(http_utils::HasFrameBustingHeader( 477 "X-Frame-Options: SAMEorigin")); 478 479 // Verify that we pick up case changes in the header name too: 480 EXPECT_TRUE(http_utils::HasFrameBustingHeader("X-FRAME-OPTIONS: deny")); 481 EXPECT_TRUE(http_utils::HasFrameBustingHeader("x-frame-options: deny")); 482 EXPECT_TRUE(http_utils::HasFrameBustingHeader("X-frame-optionS: deny")); 483 EXPECT_TRUE(http_utils::HasFrameBustingHeader("X-Frame-optionS: deny")); 484 485 // Allowall entries do not override the denying entries, are 486 // order-independent, and the deny entries can interleave with 487 // other headers. 488 EXPECT_TRUE(http_utils::HasFrameBustingHeader( 489 "Content-Length: 42\r\n" 490 "X-Frame-Options: ALLOWall\r\n" 491 "X-Frame-Options: deny\r\n")); 492 EXPECT_TRUE(http_utils::HasFrameBustingHeader( 493 "X-Frame-Options: ALLOWall\r\n" 494 "Content-Length: 42\r\n" 495 "X-Frame-Options: SAMEORIGIN\r\n")); 496 EXPECT_TRUE(http_utils::HasFrameBustingHeader( 497 "X-Frame-Options: deny\r\n" 498 "X-Frame-Options: ALLOWall\r\n" 499 "Content-Length: 42\r\n")); 500 EXPECT_TRUE(http_utils::HasFrameBustingHeader( 501 "X-Frame-Options: SAMEORIGIN\r\n" 502 "X-Frame-Options: ALLOWall\r\n")); 503 } 504