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.
6 #include <atlsecurity.h>
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"
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"
28 const char kChromeFrameUserAgent
[] = "chromeframe";
30 class HtmlUtilUnittest
: public testing::Test
{
35 // Returns the test path given a test case.
36 virtual bool GetTestPath(const std::string
& test_case
, base::FilePath
* path
) {
42 base::FilePath test_path
;
43 if (!PathService::Get(base::DIR_SOURCE_ROOT
, &test_path
)) {
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
);
57 virtual bool GetTestData(const std::string
& test_case
, std::wstring
* data
) {
64 if (!GetTestPath(test_case
, &path
)) {
70 file_util::ReadFileToString(path
, &raw_data
);
72 // Convert to wide using the "best effort" assurance described in
74 data
->assign(UTF8ToWide(raw_data
));
79 TEST_F(HtmlUtilUnittest
, BasicTest
) {
80 std::wstring test_data
;
81 GetTestData("basic_test.html", &test_data
);
83 HTMLScanner
scanner(test_data
.c_str());
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());
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"));
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"));
100 TEST_F(HtmlUtilUnittest
, QuotesTest
) {
101 std::wstring test_data
;
102 GetTestData("quotes_test.html", &test_data
);
104 HTMLScanner
scanner(test_data
.c_str());
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());
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"));
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"));
121 TEST_F(HtmlUtilUnittest
, DegenerateCasesTest
) {
122 std::wstring test_data
;
123 GetTestData("degenerate_cases_test.html", &test_data
);
125 HTMLScanner
scanner(test_data
.c_str());
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());
134 TEST_F(HtmlUtilUnittest
, MultipleTagsTest
) {
135 std::wstring test_data
;
136 GetTestData("multiple_tags.html", &test_data
);
138 HTMLScanner
scanner(test_data
.c_str());
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());
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(
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"));
159 EXPECT_EQ(3, valid_tag_count
);
162 TEST_F(HtmlUtilUnittest
, ShortDegenerateTest1
) {
163 std::wstring
test_data(
164 L
"<foo><META http-equiv=X-UA-Compatible content='chrome=1'");
166 HTMLScanner
scanner(test_data
.c_str());
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());
175 TEST_F(HtmlUtilUnittest
, ShortDegenerateTest2
) {
176 std::wstring
test_data(
177 L
"<foo <META http-equiv=X-UA-Compatible content='chrome=1'/>");
179 HTMLScanner
scanner(test_data
.c_str());
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());
188 TEST_F(HtmlUtilUnittest
, QuoteInsideHTMLCommentTest
) {
189 std::wstring
test_data(
190 L
"<!-- comment' --><META http-equiv=X-UA-Compatible content='chrome=1'/>");
192 HTMLScanner
scanner(test_data
.c_str());
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());
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"));
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"));
209 TEST_F(HtmlUtilUnittest
, CloseTagInsideHTMLCommentTest
) {
210 std::wstring
test_data(
211 L
"<!-- comment> <META http-equiv=X-UA-Compatible content='chrome=1'/>-->");
213 HTMLScanner
scanner(test_data
.c_str());
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());
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'/>"
226 HTMLScanner
scanner(test_data
.c_str());
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());
234 TEST_F(HtmlUtilUnittest
, IEConditionalCommentWithNestedCommentTest
) {
235 std::wstring
test_data(
236 L
"<!--[if IE]><!--<META http-equiv=X-UA-Compatible content='chrome=1'/>"
239 HTMLScanner
scanner(test_data
.c_str());
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());
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>");
253 HTMLScanner
scanner(test_data
.c_str());
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());
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());
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());
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>");
277 HTMLScanner
scanner(test_data
.c_str());
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());
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());
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());
295 TEST_F(HtmlUtilUnittest
, IEConditionalCommentNonTerminatedTest
) {
296 // This test shouldn't detect any tags up until the end of the conditional
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>");
303 HTMLScanner
scanner(test_data
.c_str());
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());
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());
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());
321 struct UserAgentTestCase
{
323 std::string expected_
;
324 } user_agent_test_cases
[] = {
328 "Mozilla/4.7 [en] (WinNT; U)",
329 "Mozilla/4.7 [en] (WinNT; U; chromeframe/0.0.0.0)"
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)"
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)"
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) "
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)"
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"
352 "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.6) Gecko/20040612 "
354 "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.6; chromeframe/0.0.0.0) "
355 "Gecko/20040612 Firefox/0.8"
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)"
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",
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"
370 "Invalid/1.1 ((((((",
371 "Invalid/1.1 (((((( chromeframe/0.0.0.0",
373 "Invalid/1.1 ()))))",
374 "Invalid/1.1 ( chromeframe/0.0.0.0)))))",
377 "Strange/1.1 ( chromeframe/0.0.0.0)",
381 TEST_F(HtmlUtilUnittest
, AddChromeFrameToUserAgentValue
) {
382 for (int i
= 0; i
< arraysize(user_agent_test_cases
); ++i
) {
384 http_utils::AddChromeFrameToUserAgentValue(
385 user_agent_test_cases
[i
].input_
));
386 EXPECT_EQ(user_agent_test_cases
[i
].expected_
, new_ua
);
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
);
398 TEST_F(HtmlUtilUnittest
, RemoveChromeFrameFromUserAgentValue
) {
399 for (int i
= 0; i
< arraysize(user_agent_test_cases
); ++i
) {
401 http_utils::RemoveChromeFrameFromUserAgentValue(
402 user_agent_test_cases
[i
].expected_
));
403 EXPECT_EQ(user_agent_test_cases
[i
].input_
, new_ua
);
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
);
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
));
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
));
429 const char* ua
= http_utils::GetChromeUserAgent();
430 EXPECT_EQ(ua
, chrome_ua
);
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"));
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
);
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"));
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"));
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"));
485 // Allowall entries do not override the denying entries, are
486 // order-independent, and the deny entries can interleave with
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"));