// 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 "chrome_frame/test/ie_configurator.h" #include #include #include #include #include #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "base/string16.h" #include "base/time.h" #include "base/win/registry.h" #include "chrome_frame/chrome_tab.h" #include "chrome_frame/test/chrome_frame_test_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace chrome_frame_test { namespace { const wchar_t kKeyIEApprovedExtensions[] = L"Software\\Microsoft\\Internet Explorer\\Approved Extensions\\"; const wchar_t kKeyIEInformationBar[] = L"Software\\Microsoft\\Internet Explorer\\InformationBar"; const wchar_t kKeyIEMain[] = L"Software\\Microsoft\\Internet Explorer\\Main"; const wchar_t kKeyIEPhishingFilter[] = L"Software\\Microsoft\\Internet Explorer\\PhishingFilter"; const wchar_t kKeyIEBrowserEmulation[] = L"Software\\Microsoft\\Internet Explorer\\BrowserEmulation"; const wchar_t kKeyPoliciesExt[] = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Ext"; const wchar_t kValueEnabledV9[] = L"EnabledV9"; const wchar_t kValueFirstTime[] = L"FirstTime"; const wchar_t kValueIE9Completed[] = L"IE9RunOncePerInstallCompleted"; const wchar_t kValueIE9CompletionTime[] = L"IE9RunOnceCompletionTime"; const wchar_t kValueIE9LastShown[] = L"IE9RunOnceLastShown"; const wchar_t kValueIE9TourNoShow[] = L"IE9TourNoShow"; const wchar_t kValueIE10Completed[] = L"IE10RunOncePerInstallCompleted"; const wchar_t kValueIE10CompletionTime[] = L"IE10RunOnceCompletionTime"; const wchar_t kValueIgnoreFrameApprovalCheck[] = L"IgnoreFrameApprovalCheck"; const wchar_t kValueMSCompatibilityMode[] = L"MSCompatibilityMode"; // A helper class that accumulate a set of registry mutations and corresponding // undo data via calls to its Add*() methods. The mutations can be applied to // the registry (repeatedly, if desired) via a call to Apply(). Revert() can be // called to apply the accumulated undo data. class RegistrySetter { public: RegistrySetter(); ~RegistrySetter(); // Adds a mutation that sets a REG_DWORD registry value, creating any // intermediate keys as necessary. |key| and |value| must remain valid // throughout all calls to Apply(). void AddDWORDValue(HKEY root, const wchar_t* key, const wchar_t* value, DWORD data); // Adds a mutation that assigns a FILETIME to a REG_BINARY registry value, // creating any intermediate keys as necessary. |key| and |value| must remain // valid throughout all calls to Apply(). void AddFILETIMEValue(HKEY root, const wchar_t* key, const wchar_t* value, FILETIME data); // Applies all mutations in the order they were added. Errors encountered // along the way are logged, but do not stop progress. void Apply() const; // Applies the undo data in the reverse order that their corresponding // mutations were added. void Revert() const; private: // The data for an individual registry value. A non-existent value is // indicated by type = REG_NONE and empty data. struct RegistryData { HKEY root; const wchar_t* key; const wchar_t* value; DWORD type; std::vector data; }; typedef std::list RegistryDataList; // Adds a mutation to the end of the apply list with the given data, and a // mutation to the revert list with the current state of the value. void AddValue(HKEY root, const wchar_t* key, const wchar_t* value, DWORD type, const uint8* value_begin, size_t value_len); // Add a mutation to the revert list that restores a registry value to its // current state. This only adds entries to set or remove |value|. If // portions of the hierarchy identified by |key| do not exist, but are created // between the invocation of this method and the time the revert list is // applied, only |value| is deleted upon revert (intermediate keys are not // deleted). void SaveCurrentValue(HKEY root, const wchar_t* key, const wchar_t* value); // Applies all mutations in |data_list| in order. static void ApplyList(const RegistryDataList& data_list); RegistryDataList apply_list_; RegistryDataList revert_list_; DISALLOW_COPY_AND_ASSIGN(RegistrySetter); }; // A Google Test event listener that delegates to a configurator. class ConfiguratorDriver : public testing::EmptyTestEventListener { public: explicit ConfiguratorDriver(IEConfigurator* configurator); virtual ~ConfiguratorDriver(); virtual void OnTestProgramStart(const testing::UnitTest& unit_test) OVERRIDE; virtual void OnTestStart(const testing::TestInfo& test_info) OVERRIDE; virtual void OnTestProgramEnd(const testing::UnitTest& unit_test) OVERRIDE; private: scoped_ptr configurator_; DISALLOW_COPY_AND_ASSIGN(ConfiguratorDriver); }; // A configurator for Internet Explorer 7. class IE7Configurator : public IEConfigurator { public: IE7Configurator(); virtual ~IE7Configurator(); virtual void Initialize() OVERRIDE; virtual void ApplySettings() OVERRIDE; virtual void RevertSettings() OVERRIDE; private: RegistrySetter setter_; DISALLOW_COPY_AND_ASSIGN(IE7Configurator); }; // A configurator for Internet Explorer 9 and 10. class ModernIEConfigurator : public IEConfigurator { public: explicit ModernIEConfigurator(IEVersion ie_version); virtual ~ModernIEConfigurator(); virtual void Initialize() OVERRIDE; virtual void ApplySettings() OVERRIDE; virtual void RevertSettings() OVERRIDE; private: // The names of the registry values used to determine if IE's one-time // initialization has been completed. struct RunOnceValueNames { // This DWORD value is non-zero once initialization has been completed. const wchar_t* completed; // This 8-byte binary value is the FILETIME of completion. const wchar_t* completion_time; }; static const RunOnceValueNames kIE9ValueNames; static const RunOnceValueNames kIE10ValueNames; static const RunOnceValueNames* RunOnceNamesForVersion(IEVersion ie_version); bool IsPerUserSetupComplete(); static string16 GetChromeFrameBHOCLSID(); static bool IsAddonPromptDisabledForChromeFrame(); const IEVersion ie_version_; const RunOnceValueNames* run_once_value_names_; RegistrySetter setter_; DISALLOW_COPY_AND_ASSIGN(ModernIEConfigurator); }; // RegistrySetter implementation. RegistrySetter::RegistrySetter() { } RegistrySetter::~RegistrySetter() { } void RegistrySetter::AddValue(HKEY root, const wchar_t* key, const wchar_t* value, DWORD type, const uint8* value_begin, size_t value_len) { RegistryData the_data = { root, key, value, type, std::vector(value_begin, value_begin + value_len) }; apply_list_.push_back(the_data); SaveCurrentValue(root, key, value); } void RegistrySetter::SaveCurrentValue(HKEY root, const wchar_t* key, const wchar_t* value) { base::win::RegKey the_key; RegistryData the_data = { root, key, value, REG_NONE }; LONG result = the_key.Open(root, key, KEY_QUERY_VALUE); if (result == ERROR_SUCCESS) { DWORD size = 0; result = the_key.ReadValue(value, NULL, &size, &the_data.type); if (result == ERROR_FILE_NOT_FOUND) { // Add a mutation to delete the value. the_data.type = REG_NONE; revert_list_.push_front(the_data); } else if (result == ERROR_SUCCESS) { the_data.data.resize(size); result = the_key.ReadValue(value, &the_data.data[0], &size, &the_data.type); if (result == ERROR_SUCCESS) { revert_list_.push_front(the_data); } else { ::SetLastError(result); PLOG(ERROR) << __FUNCTION__ << " unexpected error reading data for " << value << " from key " << key << ". The current value will not be restored upon revert."; } } else { ::SetLastError(result); PLOG(ERROR) << __FUNCTION__ << " unexpected error reading " << value << " from key " << key << ". The current value will not be restored upon revert."; } } else if (result == ERROR_FILE_NOT_FOUND) { // Add a mutation to delete the value (but not any keys). revert_list_.push_front(the_data); } else { ::SetLastError(result); PLOG(ERROR) << __FUNCTION__ << " unexpected error opening key " << key << " to read value " << value << ". The current value will not be restored upon revert."; } } void RegistrySetter::AddDWORDValue(HKEY root, const wchar_t* key, const wchar_t* value, DWORD data) { const uint8* data_ptr = reinterpret_cast(&data); AddValue(root, key, value, REG_DWORD, data_ptr, sizeof(data)); } void RegistrySetter::AddFILETIMEValue(HKEY root, const wchar_t* key, const wchar_t* value, FILETIME data) { const uint8* data_ptr = reinterpret_cast(&data); AddValue(root, key, value, REG_BINARY, data_ptr, sizeof(data)); } // static void RegistrySetter::ApplyList(const RegistryDataList& data_list) { base::win::RegKey key; LONG result = ERROR_SUCCESS; for (RegistryDataList::const_iterator scan(data_list.begin()); scan != data_list.end(); ++scan) { const RegistryData& data = *scan; const bool do_delete = (data.type == REG_NONE && data.data.empty()); result = do_delete ? key.Open(data.root, data.key, KEY_SET_VALUE) : key.Create(data.root, data.key, KEY_SET_VALUE); if (result == ERROR_SUCCESS) { if (do_delete) { result = key.DeleteValue(data.value); } else { result = key.WriteValue(data.value, data.data.empty() ? NULL : &data.data[0], data.data.size(), data.type); } if (result != ERROR_SUCCESS) { ::SetLastError(result); PLOG(ERROR) << "Failed to " << (do_delete ? "delete" : "set") << " value " << data.value << " in registry key " << data.key << " in hive " << std::hex <Initialize(); } void ConfiguratorDriver::OnTestStart(const testing::TestInfo& test_info) { configurator_->ApplySettings(); } void ConfiguratorDriver::OnTestProgramEnd(const testing::UnitTest& unit_test) { configurator_->RevertSettings(); } // IE7Configurator implementation IE7Configurator::IE7Configurator() { } IE7Configurator::~IE7Configurator() { } void IE7Configurator::Initialize() { // Suppress the friendly "Hi! I popped up an info bar for you. Did you see // the info bar I just showed you? There's a yellow thing in your IE window // that wasn't there before! I call it an Information Bar. Did you notice // the Information Bar?" dialog that it likes to show. setter_.AddDWORDValue(HKEY_CURRENT_USER, kKeyIEInformationBar, kValueFirstTime, 0); } void IE7Configurator::ApplySettings() { setter_.Apply(); } void IE7Configurator::RevertSettings() { setter_.Revert(); } // ModernIEConfigurator implementation const ModernIEConfigurator::RunOnceValueNames ModernIEConfigurator::kIE9ValueNames = { kValueIE9Completed, kValueIE9CompletionTime, }; const ModernIEConfigurator::RunOnceValueNames ModernIEConfigurator::kIE10ValueNames = { kValueIE10Completed, kValueIE10CompletionTime, }; ModernIEConfigurator::ModernIEConfigurator(IEVersion ie_version) : ie_version_(ie_version), run_once_value_names_(RunOnceNamesForVersion(ie_version)) { } ModernIEConfigurator::~ModernIEConfigurator() { } // static const ModernIEConfigurator::RunOnceValueNames* ModernIEConfigurator::RunOnceNamesForVersion( IEVersion ie_version) { switch (ie_version) { case IE_9: return &kIE9ValueNames; break; case IE_10: return &kIE10ValueNames; break; default: NOTREACHED(); } return NULL; } // Returns true if the per-user setup is complete. bool ModernIEConfigurator::IsPerUserSetupComplete() { bool is_complete = false; base::win::RegKey key_main; if (key_main.Open(HKEY_CURRENT_USER, kKeyIEMain, KEY_QUERY_VALUE) == ERROR_SUCCESS) { DWORD completed = 0; FILETIME completion_time = {}; DWORD size = sizeof(completion_time); if (key_main.ReadValueDW(run_once_value_names_->completed, &completed) == ERROR_SUCCESS && completed != 0 && key_main.ReadValue(run_once_value_names_->completion_time, &completion_time, &size, NULL) == ERROR_SUCCESS && size == sizeof(completion_time)) { is_complete = true; } } return is_complete; } // Returns the path to the IE9 Approved Extensions key for Chrome Frame. // static string16 ModernIEConfigurator::GetChromeFrameBHOCLSID() { string16 bho_guid(39, L'\0'); int guid_len = StringFromGUID2(CLSID_ChromeFrameBHO, &bho_guid[0], bho_guid.size()); DCHECK_EQ(guid_len, static_cast(bho_guid.size())); bho_guid.resize(guid_len - 1); return bho_guid; } // Returns true if the add-on enablement prompt is disabled by Group Policy. // static bool ModernIEConfigurator::IsAddonPromptDisabledForChromeFrame() { bool is_disabled = false; base::win::RegKey key; // See if the prompt is disabled for this user of IE. if (key.Open(HKEY_CURRENT_USER, kKeyIEApprovedExtensions, KEY_QUERY_VALUE) == ERROR_SUCCESS) { DWORD type = REG_NONE; DWORD size = 0; if (key.ReadValue(GetChromeFrameBHOCLSID().c_str(), NULL, &size, &type) == ERROR_SUCCESS && type == REG_BINARY) { is_disabled = true; } } // Now check if the prompt is disabled for all add-ons via Group Policy. if (!is_disabled && key.Open(HKEY_LOCAL_MACHINE, kKeyPoliciesExt, KEY_QUERY_VALUE) == ERROR_SUCCESS) { DWORD ignore = 0; if (key.ReadValueDW(kValueIgnoreFrameApprovalCheck, &ignore) == ERROR_SUCCESS && ignore != 0) { is_disabled = true; } } return is_disabled; } void ModernIEConfigurator::Initialize() { // Check for per-user IE setup. if (!IsPerUserSetupComplete()) { const HKEY root = HKEY_CURRENT_USER; // Suppress the "Set up Internet Explorer" dialog. setter_.AddDWORDValue(root, kKeyIEMain, run_once_value_names_->completed, 1); setter_.AddFILETIMEValue(root, kKeyIEMain, run_once_value_names_->completion_time, base::Time::Now().ToFileTime()); if (ie_version_ == IE_9) { setter_.AddDWORDValue(root, kKeyIEMain, kValueIE9LastShown, 1); // Don't show a tour of IE 9. setter_.AddDWORDValue(root, kKeyIEMain, kValueIE9TourNoShow, 1); } // Turn off the phishing filter. setter_.AddDWORDValue(root, kKeyIEPhishingFilter, kValueEnabledV9, 0); // Don't download compatibility view lists. setter_.AddDWORDValue(root, kKeyIEBrowserEmulation, kValueMSCompatibilityMode, 0); } // Turn off the "'Foo' add-on from 'Bar' is ready for use." prompt via Group // Policy. if (!IsAddonPromptDisabledForChromeFrame()) { setter_.AddDWORDValue(HKEY_LOCAL_MACHINE, kKeyPoliciesExt, kValueIgnoreFrameApprovalCheck, 1); } } void ModernIEConfigurator::ApplySettings() { setter_.Apply(); } void ModernIEConfigurator::RevertSettings() { setter_.Revert(); } } // namespace // Configurator implementation. IEConfigurator::IEConfigurator() { } IEConfigurator::~IEConfigurator() { } IEConfigurator* CreateConfigurator() { IEConfigurator* configurator = NULL; IEVersion ie_version = GetInstalledIEVersion(); switch (ie_version) { case IE_7: configurator = new IE7Configurator(); break; case IE_9: case IE_10: configurator = new ModernIEConfigurator(ie_version); break; default: break; } return configurator; } void InstallIEConfigurator() { IEConfigurator* configurator = CreateConfigurator(); if (configurator != NULL) { testing::UnitTest::GetInstance()->listeners().Append( new ConfiguratorDriver(configurator)); } } } // namespace chrome_frame_test