diff options
author | erikwright@chromium.org <erikwright@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-25 14:14:39 +0000 |
---|---|---|
committer | erikwright@chromium.org <erikwright@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-25 14:14:39 +0000 |
commit | b57f0323eac5ecbbffd826abd92620901b55fd3a (patch) | |
tree | d907bb347a9aa2668213f27eee3eb257dbba41b3 | |
parent | 543bcda1772cdd04e39fc8d912f61155ff570b9f (diff) | |
download | chromium_src-b57f0323eac5ecbbffd826abd92620901b55fd3a.zip chromium_src-b57f0323eac5ecbbffd826abd92620901b55fd3a.tar.gz chromium_src-b57f0323eac5ecbbffd826abd92620901b55fd3a.tar.bz2 |
Enable developers to conditionally activate Chrome Frame, depending on the version of the host IE browser.
BUG=52601
TEST=[chrome_frame_unittests / UtilTests.XUaCompatibleDirectiveTest], [chrome_frame_tests / HeaderTest.*]
Review URL: http://codereview.chromium.org/3978001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@63728 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome_frame/chrome_frame.gyp | 1 | ||||
-rw-r--r-- | chrome_frame/protocol_sink_wrap.cc | 14 | ||||
-rw-r--r-- | chrome_frame/test/header_test.cc | 184 | ||||
-rw-r--r-- | chrome_frame/test/util_unittests.cc | 59 | ||||
-rw-r--r-- | chrome_frame/utils.cc | 95 | ||||
-rw-r--r-- | chrome_frame/utils.h | 46 |
6 files changed, 366 insertions, 33 deletions
diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp index cde3108..9a99434 100644 --- a/chrome_frame/chrome_frame.gyp +++ b/chrome_frame/chrome_frame.gyp @@ -254,6 +254,7 @@ 'test/chrome_frame_automation_mock.cc', 'test/chrome_frame_automation_mock.h', 'test/delete_chrome_history_test.cc', + 'test/header_test.cc', 'test/http_server.cc', 'test/http_server.h', 'test/ie_event_sink.cc', diff --git a/chrome_frame/protocol_sink_wrap.cc b/chrome_frame/protocol_sink_wrap.cc index 7e3a760..bb47a5d 100644 --- a/chrome_frame/protocol_sink_wrap.cc +++ b/chrome_frame/protocol_sink_wrap.cc @@ -312,12 +312,13 @@ RendererType DetermineRendererTypeFromMetaData( } if (info) { - char buffer[32] = "x-ua-compatible"; + char buffer[512] = "x-ua-compatible"; DWORD len = sizeof(buffer); DWORD flags = 0; HRESULT hr = info->QueryInfo(HTTP_QUERY_CUSTOM, buffer, &len, &flags, NULL); + if (hr == S_OK && len > 0) { - if (StrStrIA(buffer, "chrome=1")) { + if (CheckXUaCompatibleDirective(buffer, GetIEMajorVersion())) { return RENDERER_TYPE_CHROME_RESPONSE_HEADER; } } @@ -345,9 +346,12 @@ RendererType DetermineRendererType(void* buffer, DWORD size, bool last_chance) { // browsers may handle this properly, we don't and will stop scanning // for the XUACompat content value if we encounter one. std::wstring xua_compat_content; - UtilGetXUACompatContentValue(html_contents, &xua_compat_content); - if (StrStrI(xua_compat_content.c_str(), kChromeContentPrefix)) { - renderer_type = RENDERER_TYPE_CHROME_HTTP_EQUIV; + if (SUCCEEDED(UtilGetXUACompatContentValue(html_contents, + &xua_compat_content))) { + if (CheckXUaCompatibleDirective(WideToASCII(xua_compat_content), + GetIEMajorVersion())) { + renderer_type = RENDERER_TYPE_CHROME_HTTP_EQUIV; + } } return renderer_type; diff --git a/chrome_frame/test/header_test.cc b/chrome_frame/test/header_test.cc new file mode 100644 index 0000000..1aedbd9 --- /dev/null +++ b/chrome_frame/test/header_test.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2010 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 "chrome_frame/test/mock_ie_event_sink_actions.h" +#include "chrome_frame/test/mock_ie_event_sink_test.h" +#include "base/rand_util.h" + +namespace chrome_frame_test { + +class TestData { + public: + TestData(const std::string& value, bool in_header, LoadedInRenderer expected) + : value_(value), + in_header_(in_header), + expected_(expected), + name_(base::IntToString(base::RandInt(0, 1000))) { + } + + LoadedInRenderer GetExpectedRenderer() const { + return expected_; + } + + std::wstring GetPath() const { + return ASCIIToWide("/" + name_); + } + + std::wstring GetUrl(MockWebServer* server_mock) const { + return server_mock->Resolve(ASCIIToWide(name_)); + } + + void ExpectOnServer(MockWebServer* server_mock) const { + EXPECT_CALL(*server_mock, Get(testing::_, GetPath(), testing::_)) + .Times(testing::AnyNumber()) + .WillRepeatedly(SendFast(GetHeaders(), GetHtml())); + } + + std::string GetHeaders() const { + std::ostringstream headers; + headers << "HTTP/1.1 200 OK\r\n" + << "Connection: close\r\n" + << "Content-Type: text/html\r\n"; + if (in_header_) { + headers << "X-UA-COMPATIBLE: " << value_ << "\r\n"; + } + return headers.str(); + } + + std::string GetHtml() const { + std::ostringstream html; + html << "<html><head>"; + if (!in_header_) { + html << "<meta http-equiv=\"x-ua-compatible\" content=\"" << value_ + << "\" />"; + } + html << "</head><body>This is some text.</body></html>"; + return html.str(); + } + + private: + LoadedInRenderer expected_; + std::string name_; + std::string value_; + bool in_header_; +}; + +// Determines the major version of the installed IE +// Returns -1 in case of failure, 0 if the version is newer than currently known +int GetIEMajorVersion() { + switch (GetInstalledIEVersion()) { + case IE_6: + return 6; + case IE_7: + return 7; + case IE_8: + return 8; + case IE_9: + return 9; + case IE_INVALID: + case NON_IE: + case IE_UNSUPPORTED: + ADD_FAILURE() << "Failed to detect IE version."; + return -1; + default: + return 0; + } +} + +int LowVersion() { + int ie_version = GetIEMajorVersion(); + switch (ie_version) { + case -1: + case 0: + return 5; + default: + return ie_version - 1; + } +} + +int HighVersion() { + int ie_version = GetIEMajorVersion(); + switch (ie_version) { + case -1: + case 0: + return 1000; + default: + return ie_version + 1; + } +} + +int EqualVersion() { + int ie_version = GetIEMajorVersion(); + switch (ie_version) { + case -1: + case 0: + return 1000; + default: + return ie_version; + } +} + +std::string HeaderValue(int ie_version) { + if (ie_version == -1) { + return "IE=8; Chrome=1"; + } else { + return std::string("IE=8; Chrome=IE") + base::IntToString(ie_version); + } +} + +std::string CorruptHeaderValue(int ie_version) { + return HeaderValue(ie_version) + ".0"; +} + +std::string LongHeaderValue(int ie_version) { + std::string long_value = HeaderValue(ie_version) + "; " + + std::string(256, 'a') + "=bbb"; + DCHECK_GT(long_value.length(), 256u); + DCHECK_LT(long_value.length(), 512u); + return long_value; +} + +class HeaderTest + : public MockIEEventSinkTest, + public testing::TestWithParam<TestData> { + public: + HeaderTest() {} +}; + +INSTANTIATE_TEST_CASE_P(MetaTag, HeaderTest, testing::Values( + TestData(HeaderValue(LowVersion()), false, IN_IE), + TestData(HeaderValue(EqualVersion()), false, IN_CF), + TestData(HeaderValue(HighVersion()), false, IN_CF), + TestData(LongHeaderValue(LowVersion()), false, IN_IE), + TestData(LongHeaderValue(EqualVersion()), false, IN_CF), + TestData(LongHeaderValue(HighVersion()), false, IN_CF))); +INSTANTIATE_TEST_CASE_P(HttpHeader, HeaderTest, testing::Values( + TestData(HeaderValue(LowVersion()), true, IN_IE), + TestData(HeaderValue(EqualVersion()), true, IN_CF), + TestData(HeaderValue(HighVersion()), true, IN_CF), + TestData(LongHeaderValue(LowVersion()), true, IN_IE), + TestData(LongHeaderValue(EqualVersion()), true, IN_CF), + TestData(LongHeaderValue(HighVersion()), true, IN_CF))); +INSTANTIATE_TEST_CASE_P(CorruptValueHeader, HeaderTest, testing::Values( + TestData(CorruptHeaderValue(LowVersion()), true, IN_IE), + TestData(CorruptHeaderValue(EqualVersion()), true, IN_IE), + TestData(CorruptHeaderValue(HighVersion()), true, IN_IE), + TestData(CorruptHeaderValue(LowVersion()), false, IN_IE), + TestData(CorruptHeaderValue(EqualVersion()), false, IN_IE), + TestData(CorruptHeaderValue(HighVersion()), false, IN_IE))); + +TEST_P(HeaderTest, HandleXUaCompatibleHeader) { + std::wstring url = GetParam().GetUrl(&server_mock_); + LoadedInRenderer expected_renderer = GetParam().GetExpectedRenderer(); + + GetParam().ExpectOnServer(&server_mock_); + ie_mock_.ExpectNavigation(expected_renderer, url); + + EXPECT_CALL(ie_mock_, OnLoad(expected_renderer, testing::StrEq(url))) + .WillOnce(CloseBrowserMock(&ie_mock_)); + + LaunchIEAndNavigate(url); +} + +} // namespace chrome_frame_test diff --git a/chrome_frame/test/util_unittests.cc b/chrome_frame/test/util_unittests.cc index bc18062..6f6be52 100644 --- a/chrome_frame/test/util_unittests.cc +++ b/chrome_frame/test/util_unittests.cc @@ -356,3 +356,62 @@ TEST(UtilTests, RendererTypeForUrlTest) { config_key.WriteValue(kEnableGCFRendererByDefault, saved_default_renderer); } +TEST(UtilTests, XUaCompatibleDirectiveTest) { + int all_versions[] = {0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 99, 100, 101, 1000}; + + struct Cases { + const char* header_value; + int max_version; + } test_cases[] = { + // Negative cases + { "", -1 }, + { "chrome=", -1 }, + { "chrome", -1 }, + { "chrome=X", -1 }, + { "chrome=IE", -1 }, + { "chrome=IE-7", -1 }, + { "chrome=IE+7", -1 }, + { "chrome=IE 7", -1 }, + { "chrome=IE7.0", -1 }, + { "chrome=FF7", -1 }, + { "chrome=IE7+", -1 }, + { "chrome=IE99999999999999999999", -1 }, + { "chrome=IE0", -1 }, + // Always on + { "chrome=1", INT_MAX }, + // Basic positive cases + { "chrome=IE1", 1 }, + { "CHROME=IE6", 6 }, + { "Chrome=IE10", 10 }, + { "ChRoMe=IE100", 100 }, + // Positive formatting variations + { " chrome=IE6 ", 6 }, + { " chrome=IE6; ", 6 }, + { " chrome=IE6; IE=8 ", 6 }, + { " IE=8;chrome=IE6;", 6 }, + { " IE=8;chrome=IE6;", 6 }, + { " IE=8 ; chrome = IE6 ;", 6 }, + // Ignore unrecognized values + { " IE=8 ; chrome = IE7.1; chrome = IE6;", 6 }, + // First valid wins + { " IE=8 ; chrome = IE6; chrome = IE8;", 6 } + }; + + for (int case_index = 0; case_index < arraysize(test_cases); ++case_index) { + const Cases& test = test_cases[case_index]; + + // Check that all versions <= max_version are matched + for (size_t version_index = 0; + version_index < arraysize(all_versions); + ++version_index) { + bool expect_match = (all_versions[version_index] <= test.max_version); + + ASSERT_EQ(expect_match, + CheckXUaCompatibleDirective(test.header_value, + all_versions[version_index])) + << "Expect '" << test.header_value << "' to " + << (expect_match ? "match" : "not match") << " IE major version " + << all_versions[version_index]; + } + } +} diff --git a/chrome_frame/utils.cc b/chrome_frame/utils.cc index a959adf..2e5f13e 100644 --- a/chrome_frame/utils.cc +++ b/chrome_frame/utils.cc @@ -7,13 +7,13 @@ #include <htiframe.h> #include <mshtml.h> #include <shlobj.h> -#include <wininet.h> #include <atlsecurity.h> #include "base/file_util.h" #include "base/file_version_info.h" #include "base/lazy_instance.h" +#include "base/lock.h" #include "base/logging.h" #include "base/path_service.h" #include "base/string_number_conversions.h" @@ -411,40 +411,54 @@ BrowserType GetBrowserType() { return browser_type; } -IEVersion GetIEVersion() { - static IEVersion ie_version = IE_INVALID; +uint32 GetIEMajorVersion() { + static uint32 ie_major_version = UINT_MAX; - if (ie_version == IE_INVALID) { + if (ie_major_version == UINT_MAX) { wchar_t exe_path[MAX_PATH]; HMODULE mod = GetModuleHandle(NULL); GetModuleFileName(mod, exe_path, arraysize(exe_path) - 1); std::wstring exe_name(file_util::GetFilenameFromPath(exe_path)); if (!LowerCaseEqualsASCII(exe_name, kIEImageName)) { - ie_version = NON_IE; + ie_major_version = 0; } else { uint32 high = 0; uint32 low = 0; if (GetModuleVersion(mod, &high, &low)) { - switch (HIWORD(high)) { - case 6: - ie_version = IE_6; - break; - case 7: - ie_version = IE_7; - break; - case 8: - ie_version = IE_8; - break; - default: - ie_version = HIWORD(high) >= 9 ? IE_9 : IE_UNSUPPORTED; - break; - } + ie_major_version = HIWORD(high); } else { - NOTREACHED() << "Can't get IE version"; + ie_major_version = 0; } } } + return ie_major_version; +} + +IEVersion GetIEVersion() { + static IEVersion ie_version = IE_INVALID; + + if (ie_version == IE_INVALID) { + uint32 major_version = GetIEMajorVersion(); + switch (major_version) { + case 0: + ie_version = NON_IE; + break; + case 6: + ie_version = IE_6; + break; + case 7: + ie_version = IE_7; + break; + case 8: + ie_version = IE_8; + break; + default: + ie_version = major_version >= 9 ? IE_9 : IE_UNSUPPORTED; + break; + } + } + return ie_version; } @@ -1519,6 +1533,46 @@ void WaitWithMessageLoop(HANDLE* handles, int count, DWORD timeout) { } } +bool CheckXUaCompatibleDirective(const std::string& directive, + int ie_major_version) { + net::HttpUtil::NameValuePairsIterator name_value_pairs(directive.begin(), + directive.end(), + ';'); + + // Loop through the values until a valid 'Chrome=<FILTER>' entry is found + while (name_value_pairs.GetNext()) { + if (!LowerCaseEqualsASCII(name_value_pairs.name_begin(), + name_value_pairs.name_end(), + "chrome")) { + continue; + } + std::string::const_iterator filter_begin = name_value_pairs.value_begin(); + std::string::const_iterator filter_end = name_value_pairs.value_end(); + + size_t filter_length = filter_end - filter_begin; + + if (filter_length == 1 && *filter_begin == '1') { + return true; + } + + if (filter_length < 3 || + !LowerCaseEqualsASCII(filter_begin, filter_begin + 2, "ie") || + !isdigit(*(filter_begin + 2))) { // ensure no leading +/- + continue; + } + + int header_ie_version = 0; + if (!base::StringToInt(filter_begin + 2, filter_end, &header_ie_version) || + header_ie_version == 0) { // ensure it's not a sequence of 0's + continue; + } + + // The first valid directive we find wins, whether it matches or not + return ie_major_version <= header_ie_version; + } + return false; +} + void EnumerateKeyValues(HKEY parent_key, const wchar_t* sub_key_name, std::vector<std::wstring>* values) { DCHECK(values); @@ -1528,4 +1582,3 @@ void EnumerateKeyValues(HKEY parent_key, const wchar_t* sub_key_name, ++url_list; } } - diff --git a/chrome_frame/utils.h b/chrome_frame/utils.h index 4849a803..678b86c 100644 --- a/chrome_frame/utils.h +++ b/chrome_frame/utils.h @@ -5,12 +5,12 @@ #ifndef CHROME_FRAME_UTILS_H_ #define CHROME_FRAME_UTILS_H_ -#include <shdeprecated.h> -#include <urlmon.h> +#include <OAidl.h> +#include <windows.h> #include <wininet.h> -#include <atlbase.h> #include <string> +#include <vector> #include "base/basictypes.h" #include "base/lock.h" @@ -21,6 +21,7 @@ #include "googleurl/src/gurl.h" class FilePath; +interface IBrowserService; // utils.h : Various utility functions and classes @@ -213,18 +214,25 @@ bool IsChrome(RendererType renderer_type); // To get the IE version when Chrome Frame is hosted in IE. Make sure that // the hosting browser is IE before calling this function, otherwise NON_IE // will be returned. +// +// Versions newer than the newest supported version are reported as the newest +// supported version. IEVersion GetIEVersion(); +// Returns the actual major version of the IE in which the current process is +// hosted. Returns 0 if the current process is not IE or any other error occurs. +uint32 GetIEMajorVersion(); + FilePath GetIETemporaryFilesFolder(); // Retrieves the file version from a module handle without extra round trips // to the disk (as happens with the regular GetFileVersionInfo API). // // @param module A handle to the module for which to retrieve the version info. -// @param high On successful return holds the most significant part of the -// file version. Must be non-null. -// @param low On successful return holds the least significant part of the -// file version. May be NULL. +// @param high On successful return holds the most significant part of the file +// version. Must be non-null. +// @param low On successful return holds the least significant part of the file +// version. May be NULL. // @returns true if the version info was successfully retrieved. bool GetModuleVersion(HMODULE module, uint32* high, uint32* low); @@ -596,4 +604,28 @@ void WaitWithMessageLoop(HANDLE* handles, int count, DWORD timeout); void EnumerateKeyValues(HKEY parent_key, const wchar_t* sub_key_name, std::vector<std::wstring>* values); +// Interprets the value of an X-UA-Compatible header (or <meta> tag equivalent) +// and indicates whether the header value contains a Chrome Frame directive +// matching a given host browser version. +// +// The header is a series of name-value pairs, with the names being HTTP tokens +// and the values being either tokens or quoted-strings. Names and values are +// joined by '=' and pairs are delimited by ';'. LWS may be used liberally +// before and between names, values, '=', and ';'. See RFC 2616 for definitions +// of token, quoted-string, and LWS. See Microsoft's documentation of the +// X-UA-COMPATIBLE header here: +// http://msdn.microsoft.com/en-us/library/cc288325(VS.85).aspx +// +// At most one 'Chrome=<FILTER>' entry is expected in the header value. The +// first valid instance is used. The value of "<FILTER>" (possibly after +// unquoting) is interpreted as follows: +// +// "1" - Always active +// "IE7" - Active for IE major version 7 or lower +// +// For example: +// X-UA-Compatible: IE=8; Chrome=IE6 +bool CheckXUaCompatibleDirective(const std::string& directive, + int ie_major_version); + #endif // CHROME_FRAME_UTILS_H_ |