普通文本  |  599行  |  19.38 KB

// 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 <windows.h>
#include <objbase.h>

#include <ios>
#include <list>
#include <vector>

#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string16.h"
#include "base/time/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 kValueEnabledV8[] = L"EnabledV8";
const wchar_t kValueEnabledV9[] = L"EnabledV9";
const wchar_t kValueFirstTime[] = L"FirstTime";
const wchar_t kValueIE8Completed[] = L"IE8RunOncePerInstallCompleted";
const wchar_t kValueIE8CompletionTime[] = L"IE8RunOnceCompletionTime";
const wchar_t kValueIE8RunOnceLastShown[] = L"IE8RunOnceLastShown";
const wchar_t kValueIE8RunOnceLastShownTimestamp[] =
    L"IE8RunOnceLastShown_TIMESTAMP";
const wchar_t kValueIE8TourNoShow[] = L"IE8TourNoShow";
const wchar_t kValueIE9Completed[] = L"IE9RunOncePerInstallCompleted";
const wchar_t kValueIE9CompletionTime[] = L"IE9RunOnceCompletionTime";
const wchar_t kValueIE9RunOnceLastShown[] = L"IE9RunOnceLastShown";
const wchar_t kValueIE9RunOnceLastShownTimestamp[] =
    L"IE9RunOnceLastShown_TIMESTAMP";
const wchar_t kValueIE9TourNoShow[] = L"IE9TourNoShow";
const wchar_t kValueIE10Completed[] = L"IE10RunOncePerInstallCompleted";
const wchar_t kValueIE10CompletionTime[] = L"IE10RunOnceCompletionTime";
const wchar_t kValueIE10RunOnceLastShown[] = L"IE10RunOnceLastShown";
const wchar_t kValueIE10RunOnceLastShownTimestamp[] =
    L"IE10RunOnceLastShown_TIMESTAMP";
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<uint8> data;
  };

  typedef std::list<RegistryData> 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<IEConfigurator> 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 8, 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;
    // This DWORD value is non-zero if run-once was previously deferred.
    const wchar_t* last_shown;
    // This 8-byte binary value is the FILETIME of run-once deferral.
    const wchar_t* last_shown_timestamp;
  };

  static const RunOnceValueNames kIE8ValueNames;
  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<uint8>(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<uint8*>(&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<uint8*>(&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 <<data.root << std::dec;
      }
    } else if (!do_delete || result != ERROR_FILE_NOT_FOUND) {
      ::SetLastError(result);
      PLOG(ERROR) << "Failed to create/open registry key " << data.key
                  << " in hive " << std::hex << data.root << std::dec;
    }
  }
}

void RegistrySetter::Apply() const {
  ApplyList(apply_list_);
}

void RegistrySetter::Revert() const {
  ApplyList(revert_list_);
}

// ConfiguratorDriver implementation.

ConfiguratorDriver::ConfiguratorDriver(IEConfigurator* configurator)
    : configurator_(configurator) {
  DCHECK(configurator);
}

ConfiguratorDriver::~ConfiguratorDriver() {
}

void ConfiguratorDriver::OnTestProgramStart(
    const testing::UnitTest& unit_test) {
  configurator_->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::kIE8ValueNames = {
  kValueIE8Completed,
  kValueIE8CompletionTime,
  kValueIE8RunOnceLastShown,
  kValueIE8RunOnceLastShownTimestamp,
};

const ModernIEConfigurator::RunOnceValueNames
    ModernIEConfigurator::kIE9ValueNames = {
  kValueIE9Completed,
  kValueIE9CompletionTime,
  kValueIE9RunOnceLastShown,
  kValueIE9RunOnceLastShownTimestamp,
};

const ModernIEConfigurator::RunOnceValueNames
    ModernIEConfigurator::kIE10ValueNames = {
  kValueIE10Completed,
  kValueIE10CompletionTime,
  kValueIE10RunOnceLastShown,
  kValueIE10RunOnceLastShownTimestamp,
};

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_8:
      return &kIE8ValueNames;
      break;
    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) {
    return false;
  }

  DWORD dword_value = 0;
  FILETIME shown_time = {};
  FILETIME completion_time = {};
  DWORD size = sizeof(completion_time);

  // See if the user has seen the first-run prompt.
  if (key_main.ReadValue(run_once_value_names_->last_shown_timestamp,
                         &shown_time, &size, NULL) != ERROR_SUCCESS ||
      size != sizeof(shown_time)) {
    return false;
  }

  // See if setup was completed.
  if (key_main.ReadValue(run_once_value_names_->completion_time,
                         &completion_time, &size, NULL) != ERROR_SUCCESS ||
      size != sizeof(completion_time)) {
    return false;
  }

  // See if setup was completed after the last time the prompt was shown.
  base::Time time_shown = base::Time::FromFileTime(shown_time);
  base::Time time_completed = base::Time::FromFileTime(completion_time);
  if (time_shown >= time_completed)
    return false;

  return true;
}

// 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<int>(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()) {
    base::Time time(base::Time::Now());
    const HKEY root = HKEY_CURRENT_USER;
    // Suppress the "Set up Internet Explorer" dialog.
    setter_.AddDWORDValue(root, kKeyIEMain, run_once_value_names_->last_shown,
                          1);
    setter_.AddDWORDValue(root, kKeyIEMain, run_once_value_names_->completed,
                          1);
    setter_.AddFILETIMEValue(root, kKeyIEMain,
                             run_once_value_names_->last_shown_timestamp,
                             time.ToFileTime());
    time += base::TimeDelta::FromMilliseconds(10);
    setter_.AddFILETIMEValue(root, kKeyIEMain,
                             run_once_value_names_->completion_time,
                             time.ToFileTime());
    if (ie_version_ < IE_10) {
      // Don't show a tour of IE 8 and 9.
      setter_.AddDWORDValue(
          root,
          kKeyIEMain,
          (ie_version_ == IE_8 ? kValueIE8TourNoShow : kValueIE9TourNoShow),
          1);
    }
    // Turn off the phishing filter.
    setter_.AddDWORDValue(
        root,
        kKeyIEPhishingFilter,
        (ie_version_ == IE_8 ? kValueEnabledV8 : 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_8:
    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