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 "chrome_frame/test/ie_configurator.h" 6 7 #include <windows.h> 8 #include <objbase.h> 9 10 #include <ios> 11 #include <list> 12 #include <vector> 13 14 #include "base/compiler_specific.h" 15 #include "base/memory/scoped_ptr.h" 16 #include "base/strings/string16.h" 17 #include "base/time/time.h" 18 #include "base/win/registry.h" 19 #include "chrome_frame/chrome_tab.h" 20 #include "chrome_frame/test/chrome_frame_test_utils.h" 21 #include "testing/gtest/include/gtest/gtest.h" 22 23 namespace chrome_frame_test { 24 25 namespace { 26 27 const wchar_t kKeyIEApprovedExtensions[] = 28 L"Software\\Microsoft\\Internet Explorer\\Approved Extensions\\"; 29 const wchar_t kKeyIEInformationBar[] = 30 L"Software\\Microsoft\\Internet Explorer\\InformationBar"; 31 const wchar_t kKeyIEMain[] = 32 L"Software\\Microsoft\\Internet Explorer\\Main"; 33 const wchar_t kKeyIEPhishingFilter[] = 34 L"Software\\Microsoft\\Internet Explorer\\PhishingFilter"; 35 const wchar_t kKeyIEBrowserEmulation[] = 36 L"Software\\Microsoft\\Internet Explorer\\BrowserEmulation"; 37 const wchar_t kKeyPoliciesExt[] = 38 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Ext"; 39 const wchar_t kValueEnabledV8[] = L"EnabledV8"; 40 const wchar_t kValueEnabledV9[] = L"EnabledV9"; 41 const wchar_t kValueFirstTime[] = L"FirstTime"; 42 const wchar_t kValueIE8Completed[] = L"IE8RunOncePerInstallCompleted"; 43 const wchar_t kValueIE8CompletionTime[] = L"IE8RunOnceCompletionTime"; 44 const wchar_t kValueIE8RunOnceLastShown[] = L"IE8RunOnceLastShown"; 45 const wchar_t kValueIE8RunOnceLastShownTimestamp[] = 46 L"IE8RunOnceLastShown_TIMESTAMP"; 47 const wchar_t kValueIE8TourNoShow[] = L"IE8TourNoShow"; 48 const wchar_t kValueIE9Completed[] = L"IE9RunOncePerInstallCompleted"; 49 const wchar_t kValueIE9CompletionTime[] = L"IE9RunOnceCompletionTime"; 50 const wchar_t kValueIE9RunOnceLastShown[] = L"IE9RunOnceLastShown"; 51 const wchar_t kValueIE9RunOnceLastShownTimestamp[] = 52 L"IE9RunOnceLastShown_TIMESTAMP"; 53 const wchar_t kValueIE9TourNoShow[] = L"IE9TourNoShow"; 54 const wchar_t kValueIE10Completed[] = L"IE10RunOncePerInstallCompleted"; 55 const wchar_t kValueIE10CompletionTime[] = L"IE10RunOnceCompletionTime"; 56 const wchar_t kValueIE10RunOnceLastShown[] = L"IE10RunOnceLastShown"; 57 const wchar_t kValueIE10RunOnceLastShownTimestamp[] = 58 L"IE10RunOnceLastShown_TIMESTAMP"; 59 const wchar_t kValueIgnoreFrameApprovalCheck[] = L"IgnoreFrameApprovalCheck"; 60 const wchar_t kValueMSCompatibilityMode[] = L"MSCompatibilityMode"; 61 62 // A helper class that accumulate a set of registry mutations and corresponding 63 // undo data via calls to its Add*() methods. The mutations can be applied to 64 // the registry (repeatedly, if desired) via a call to Apply(). Revert() can be 65 // called to apply the accumulated undo data. 66 class RegistrySetter { 67 public: 68 RegistrySetter(); 69 ~RegistrySetter(); 70 71 // Adds a mutation that sets a REG_DWORD registry value, creating any 72 // intermediate keys as necessary. |key| and |value| must remain valid 73 // throughout all calls to Apply(). 74 void AddDWORDValue(HKEY root, const wchar_t* key, const wchar_t* value, 75 DWORD data); 76 77 // Adds a mutation that assigns a FILETIME to a REG_BINARY registry value, 78 // creating any intermediate keys as necessary. |key| and |value| must remain 79 // valid throughout all calls to Apply(). 80 void AddFILETIMEValue(HKEY root, const wchar_t* key, const wchar_t* value, 81 FILETIME data); 82 83 // Applies all mutations in the order they were added. Errors encountered 84 // along the way are logged, but do not stop progress. 85 void Apply() const; 86 87 // Applies the undo data in the reverse order that their corresponding 88 // mutations were added. 89 void Revert() const; 90 91 private: 92 // The data for an individual registry value. A non-existent value is 93 // indicated by type = REG_NONE and empty data. 94 struct RegistryData { 95 HKEY root; 96 const wchar_t* key; 97 const wchar_t* value; 98 DWORD type; 99 std::vector<uint8> data; 100 }; 101 102 typedef std::list<RegistryData> RegistryDataList; 103 104 // Adds a mutation to the end of the apply list with the given data, and a 105 // mutation to the revert list with the current state of the value. 106 void AddValue(HKEY root, const wchar_t* key, const wchar_t* value, DWORD type, 107 const uint8* value_begin, size_t value_len); 108 109 // Add a mutation to the revert list that restores a registry value to its 110 // current state. This only adds entries to set or remove |value|. If 111 // portions of the hierarchy identified by |key| do not exist, but are created 112 // between the invocation of this method and the time the revert list is 113 // applied, only |value| is deleted upon revert (intermediate keys are not 114 // deleted). 115 void SaveCurrentValue(HKEY root, const wchar_t* key, const wchar_t* value); 116 117 // Applies all mutations in |data_list| in order. 118 static void ApplyList(const RegistryDataList& data_list); 119 120 RegistryDataList apply_list_; 121 RegistryDataList revert_list_; 122 123 DISALLOW_COPY_AND_ASSIGN(RegistrySetter); 124 }; 125 126 // A Google Test event listener that delegates to a configurator. 127 class ConfiguratorDriver : public testing::EmptyTestEventListener { 128 public: 129 explicit ConfiguratorDriver(IEConfigurator* configurator); 130 virtual ~ConfiguratorDriver(); 131 132 virtual void OnTestProgramStart(const testing::UnitTest& unit_test) OVERRIDE; 133 virtual void OnTestStart(const testing::TestInfo& test_info) OVERRIDE; 134 virtual void OnTestProgramEnd(const testing::UnitTest& unit_test) OVERRIDE; 135 136 private: 137 scoped_ptr<IEConfigurator> configurator_; 138 DISALLOW_COPY_AND_ASSIGN(ConfiguratorDriver); 139 }; 140 141 // A configurator for Internet Explorer 7. 142 class IE7Configurator : public IEConfigurator { 143 public: 144 IE7Configurator(); 145 virtual ~IE7Configurator(); 146 147 virtual void Initialize() OVERRIDE; 148 virtual void ApplySettings() OVERRIDE; 149 virtual void RevertSettings() OVERRIDE; 150 151 private: 152 RegistrySetter setter_; 153 154 DISALLOW_COPY_AND_ASSIGN(IE7Configurator); 155 }; 156 157 // A configurator for Internet Explorer 8, 9, and 10. 158 class ModernIEConfigurator : public IEConfigurator { 159 public: 160 explicit ModernIEConfigurator(IEVersion ie_version); 161 virtual ~ModernIEConfigurator(); 162 163 virtual void Initialize() OVERRIDE; 164 virtual void ApplySettings() OVERRIDE; 165 virtual void RevertSettings() OVERRIDE; 166 167 private: 168 // The names of the registry values used to determine if IE's one-time 169 // initialization has been completed. 170 struct RunOnceValueNames { 171 // This DWORD value is non-zero once initialization has been completed. 172 const wchar_t* completed; 173 // This 8-byte binary value is the FILETIME of completion. 174 const wchar_t* completion_time; 175 // This DWORD value is non-zero if run-once was previously deferred. 176 const wchar_t* last_shown; 177 // This 8-byte binary value is the FILETIME of run-once deferral. 178 const wchar_t* last_shown_timestamp; 179 }; 180 181 static const RunOnceValueNames kIE8ValueNames; 182 static const RunOnceValueNames kIE9ValueNames; 183 static const RunOnceValueNames kIE10ValueNames; 184 185 static const RunOnceValueNames* RunOnceNamesForVersion(IEVersion ie_version); 186 bool IsPerUserSetupComplete(); 187 static string16 GetChromeFrameBHOCLSID(); 188 static bool IsAddonPromptDisabledForChromeFrame(); 189 190 const IEVersion ie_version_; 191 const RunOnceValueNames* run_once_value_names_; 192 RegistrySetter setter_; 193 194 DISALLOW_COPY_AND_ASSIGN(ModernIEConfigurator); 195 }; 196 197 // RegistrySetter implementation. 198 199 RegistrySetter::RegistrySetter() { 200 } 201 202 RegistrySetter::~RegistrySetter() { 203 } 204 205 void RegistrySetter::AddValue(HKEY root, 206 const wchar_t* key, 207 const wchar_t* value, 208 DWORD type, 209 const uint8* value_begin, 210 size_t value_len) { 211 RegistryData the_data = { 212 root, 213 key, 214 value, 215 type, 216 std::vector<uint8>(value_begin, value_begin + value_len) 217 }; 218 apply_list_.push_back(the_data); 219 SaveCurrentValue(root, key, value); 220 } 221 222 void RegistrySetter::SaveCurrentValue(HKEY root, 223 const wchar_t* key, 224 const wchar_t* value) { 225 base::win::RegKey the_key; 226 RegistryData the_data = { root, key, value, REG_NONE }; 227 228 LONG result = the_key.Open(root, key, KEY_QUERY_VALUE); 229 if (result == ERROR_SUCCESS) { 230 DWORD size = 0; 231 result = the_key.ReadValue(value, NULL, &size, &the_data.type); 232 if (result == ERROR_FILE_NOT_FOUND) { 233 // Add a mutation to delete the value. 234 the_data.type = REG_NONE; 235 revert_list_.push_front(the_data); 236 } else if (result == ERROR_SUCCESS) { 237 the_data.data.resize(size); 238 result = the_key.ReadValue(value, &the_data.data[0], &size, 239 &the_data.type); 240 if (result == ERROR_SUCCESS) { 241 revert_list_.push_front(the_data); 242 } else { 243 ::SetLastError(result); 244 PLOG(ERROR) << __FUNCTION__ << " unexpected error reading data for " 245 << value << " from key " << key 246 << ". The current value will not be restored upon revert."; 247 } 248 } else { 249 ::SetLastError(result); 250 PLOG(ERROR) << __FUNCTION__ << " unexpected error reading " << value 251 << " from key " << key 252 << ". The current value will not be restored upon revert."; 253 } 254 } else if (result == ERROR_FILE_NOT_FOUND) { 255 // Add a mutation to delete the value (but not any keys). 256 revert_list_.push_front(the_data); 257 } else { 258 ::SetLastError(result); 259 PLOG(ERROR) << __FUNCTION__ << " unexpected error opening key " << key 260 << " to read value " << value 261 << ". The current value will not be restored upon revert."; 262 } 263 } 264 265 void RegistrySetter::AddDWORDValue(HKEY root, 266 const wchar_t* key, 267 const wchar_t* value, 268 DWORD data) { 269 const uint8* data_ptr = reinterpret_cast<uint8*>(&data); 270 AddValue(root, key, value, REG_DWORD, data_ptr, sizeof(data)); 271 } 272 273 void RegistrySetter::AddFILETIMEValue(HKEY root, 274 const wchar_t* key, 275 const wchar_t* value, 276 FILETIME data) { 277 const uint8* data_ptr = reinterpret_cast<uint8*>(&data); 278 AddValue(root, key, value, REG_BINARY, data_ptr, sizeof(data)); 279 } 280 281 // static 282 void RegistrySetter::ApplyList(const RegistryDataList& data_list) { 283 base::win::RegKey key; 284 LONG result = ERROR_SUCCESS; 285 for (RegistryDataList::const_iterator scan(data_list.begin()); 286 scan != data_list.end(); ++scan) { 287 const RegistryData& data = *scan; 288 const bool do_delete = (data.type == REG_NONE && data.data.empty()); 289 result = do_delete ? 290 key.Open(data.root, data.key, KEY_SET_VALUE) : 291 key.Create(data.root, data.key, KEY_SET_VALUE); 292 if (result == ERROR_SUCCESS) { 293 if (do_delete) { 294 result = key.DeleteValue(data.value); 295 } else { 296 result = key.WriteValue(data.value, 297 data.data.empty() ? NULL : &data.data[0], 298 data.data.size(), data.type); 299 } 300 if (result != ERROR_SUCCESS) { 301 ::SetLastError(result); 302 PLOG(ERROR) << "Failed to " << (do_delete ? "delete" : "set") 303 << " value " << data.value 304 << " in registry key " << data.key 305 << " in hive " << std::hex <<data.root << std::dec; 306 } 307 } else if (!do_delete || result != ERROR_FILE_NOT_FOUND) { 308 ::SetLastError(result); 309 PLOG(ERROR) << "Failed to create/open registry key " << data.key 310 << " in hive " << std::hex << data.root << std::dec; 311 } 312 } 313 } 314 315 void RegistrySetter::Apply() const { 316 ApplyList(apply_list_); 317 } 318 319 void RegistrySetter::Revert() const { 320 ApplyList(revert_list_); 321 } 322 323 // ConfiguratorDriver implementation. 324 325 ConfiguratorDriver::ConfiguratorDriver(IEConfigurator* configurator) 326 : configurator_(configurator) { 327 DCHECK(configurator); 328 } 329 330 ConfiguratorDriver::~ConfiguratorDriver() { 331 } 332 333 void ConfiguratorDriver::OnTestProgramStart( 334 const testing::UnitTest& unit_test) { 335 configurator_->Initialize(); 336 } 337 338 void ConfiguratorDriver::OnTestStart(const testing::TestInfo& test_info) { 339 configurator_->ApplySettings(); 340 } 341 342 void ConfiguratorDriver::OnTestProgramEnd(const testing::UnitTest& unit_test) { 343 configurator_->RevertSettings(); 344 } 345 346 // IE7Configurator implementation 347 348 IE7Configurator::IE7Configurator() { 349 } 350 351 IE7Configurator::~IE7Configurator() { 352 } 353 354 void IE7Configurator::Initialize() { 355 // Suppress the friendly "Hi! I popped up an info bar for you. Did you see 356 // the info bar I just showed you? There's a yellow thing in your IE window 357 // that wasn't there before! I call it an Information Bar. Did you notice 358 // the Information Bar?" dialog that it likes to show. 359 setter_.AddDWORDValue(HKEY_CURRENT_USER, kKeyIEInformationBar, 360 kValueFirstTime, 0); 361 } 362 363 void IE7Configurator::ApplySettings() { 364 setter_.Apply(); 365 } 366 367 void IE7Configurator::RevertSettings() { 368 setter_.Revert(); 369 } 370 371 // ModernIEConfigurator implementation 372 373 const ModernIEConfigurator::RunOnceValueNames 374 ModernIEConfigurator::kIE8ValueNames = { 375 kValueIE8Completed, 376 kValueIE8CompletionTime, 377 kValueIE8RunOnceLastShown, 378 kValueIE8RunOnceLastShownTimestamp, 379 }; 380 381 const ModernIEConfigurator::RunOnceValueNames 382 ModernIEConfigurator::kIE9ValueNames = { 383 kValueIE9Completed, 384 kValueIE9CompletionTime, 385 kValueIE9RunOnceLastShown, 386 kValueIE9RunOnceLastShownTimestamp, 387 }; 388 389 const ModernIEConfigurator::RunOnceValueNames 390 ModernIEConfigurator::kIE10ValueNames = { 391 kValueIE10Completed, 392 kValueIE10CompletionTime, 393 kValueIE10RunOnceLastShown, 394 kValueIE10RunOnceLastShownTimestamp, 395 }; 396 397 ModernIEConfigurator::ModernIEConfigurator(IEVersion ie_version) 398 : ie_version_(ie_version), 399 run_once_value_names_(RunOnceNamesForVersion(ie_version)) { 400 } 401 402 ModernIEConfigurator::~ModernIEConfigurator() { 403 } 404 405 // static 406 const ModernIEConfigurator::RunOnceValueNames* 407 ModernIEConfigurator::RunOnceNamesForVersion( 408 IEVersion ie_version) { 409 switch (ie_version) { 410 case IE_8: 411 return &kIE8ValueNames; 412 break; 413 case IE_9: 414 return &kIE9ValueNames; 415 break; 416 case IE_10: 417 return &kIE10ValueNames; 418 break; 419 default: 420 NOTREACHED(); 421 } 422 return NULL; 423 } 424 425 // Returns true if the per-user setup is complete. 426 bool ModernIEConfigurator::IsPerUserSetupComplete() { 427 bool is_complete = false; 428 base::win::RegKey key_main; 429 430 if (key_main.Open(HKEY_CURRENT_USER, kKeyIEMain, 431 KEY_QUERY_VALUE) != ERROR_SUCCESS) { 432 return false; 433 } 434 435 DWORD dword_value = 0; 436 FILETIME shown_time = {}; 437 FILETIME completion_time = {}; 438 DWORD size = sizeof(completion_time); 439 440 // See if the user has seen the first-run prompt. 441 if (key_main.ReadValue(run_once_value_names_->last_shown_timestamp, 442 &shown_time, &size, NULL) != ERROR_SUCCESS || 443 size != sizeof(shown_time)) { 444 return false; 445 } 446 447 // See if setup was completed. 448 if (key_main.ReadValue(run_once_value_names_->completion_time, 449 &completion_time, &size, NULL) != ERROR_SUCCESS || 450 size != sizeof(completion_time)) { 451 return false; 452 } 453 454 // See if setup was completed after the last time the prompt was shown. 455 base::Time time_shown = base::Time::FromFileTime(shown_time); 456 base::Time time_completed = base::Time::FromFileTime(completion_time); 457 if (time_shown >= time_completed) 458 return false; 459 460 return true; 461 } 462 463 // Returns the path to the IE9 Approved Extensions key for Chrome Frame. 464 // static 465 string16 ModernIEConfigurator::GetChromeFrameBHOCLSID() { 466 string16 bho_guid(39, L'\0'); 467 int guid_len = StringFromGUID2(CLSID_ChromeFrameBHO, &bho_guid[0], 468 bho_guid.size()); 469 DCHECK_EQ(guid_len, static_cast<int>(bho_guid.size())); 470 bho_guid.resize(guid_len - 1); 471 return bho_guid; 472 } 473 474 // Returns true if the add-on enablement prompt is disabled by Group Policy. 475 // static 476 bool ModernIEConfigurator::IsAddonPromptDisabledForChromeFrame() { 477 bool is_disabled = false; 478 base::win::RegKey key; 479 480 // See if the prompt is disabled for this user of IE. 481 if (key.Open(HKEY_CURRENT_USER, kKeyIEApprovedExtensions, 482 KEY_QUERY_VALUE) == ERROR_SUCCESS) { 483 DWORD type = REG_NONE; 484 DWORD size = 0; 485 if (key.ReadValue(GetChromeFrameBHOCLSID().c_str(), NULL, &size, 486 &type) == ERROR_SUCCESS && 487 type == REG_BINARY) { 488 is_disabled = true; 489 } 490 } 491 492 // Now check if the prompt is disabled for all add-ons via Group Policy. 493 if (!is_disabled && 494 key.Open(HKEY_LOCAL_MACHINE, kKeyPoliciesExt, 495 KEY_QUERY_VALUE) == ERROR_SUCCESS) { 496 DWORD ignore = 0; 497 if (key.ReadValueDW(kValueIgnoreFrameApprovalCheck, 498 &ignore) == ERROR_SUCCESS && 499 ignore != 0) { 500 is_disabled = true; 501 } 502 } 503 504 return is_disabled; 505 } 506 507 void ModernIEConfigurator::Initialize() { 508 // Check for per-user IE setup. 509 if (!IsPerUserSetupComplete()) { 510 base::Time time(base::Time::Now()); 511 const HKEY root = HKEY_CURRENT_USER; 512 // Suppress the "Set up Internet Explorer" dialog. 513 setter_.AddDWORDValue(root, kKeyIEMain, run_once_value_names_->last_shown, 514 1); 515 setter_.AddDWORDValue(root, kKeyIEMain, run_once_value_names_->completed, 516 1); 517 setter_.AddFILETIMEValue(root, kKeyIEMain, 518 run_once_value_names_->last_shown_timestamp, 519 time.ToFileTime()); 520 time += base::TimeDelta::FromMilliseconds(10); 521 setter_.AddFILETIMEValue(root, kKeyIEMain, 522 run_once_value_names_->completion_time, 523 time.ToFileTime()); 524 if (ie_version_ < IE_10) { 525 // Don't show a tour of IE 8 and 9. 526 setter_.AddDWORDValue( 527 root, 528 kKeyIEMain, 529 (ie_version_ == IE_8 ? kValueIE8TourNoShow : kValueIE9TourNoShow), 530 1); 531 } 532 // Turn off the phishing filter. 533 setter_.AddDWORDValue( 534 root, 535 kKeyIEPhishingFilter, 536 (ie_version_ == IE_8 ? kValueEnabledV8 : kValueEnabledV9), 537 0); 538 // Don't download compatibility view lists. 539 setter_.AddDWORDValue(root, kKeyIEBrowserEmulation, 540 kValueMSCompatibilityMode, 0); 541 } 542 543 // Turn off the "'Foo' add-on from 'Bar' is ready for use." prompt via Group 544 // Policy. 545 if (!IsAddonPromptDisabledForChromeFrame()) { 546 setter_.AddDWORDValue(HKEY_LOCAL_MACHINE, kKeyPoliciesExt, 547 kValueIgnoreFrameApprovalCheck, 1); 548 } 549 } 550 551 void ModernIEConfigurator::ApplySettings() { 552 setter_.Apply(); 553 } 554 555 void ModernIEConfigurator::RevertSettings() { 556 setter_.Revert(); 557 } 558 559 } // namespace 560 561 // Configurator implementation. 562 563 IEConfigurator::IEConfigurator() { 564 } 565 566 IEConfigurator::~IEConfigurator() { 567 } 568 569 IEConfigurator* CreateConfigurator() { 570 IEConfigurator* configurator = NULL; 571 572 IEVersion ie_version = GetInstalledIEVersion(); 573 switch (ie_version) { 574 case IE_7: 575 configurator = new IE7Configurator(); 576 break; 577 case IE_8: 578 case IE_9: 579 case IE_10: 580 configurator = new ModernIEConfigurator(ie_version); 581 break; 582 default: 583 break; 584 } 585 586 return configurator; 587 } 588 589 void InstallIEConfigurator() { 590 IEConfigurator* configurator = CreateConfigurator(); 591 592 if (configurator != NULL) { 593 testing::UnitTest::GetInstance()->listeners().Append( 594 new ConfiguratorDriver(configurator)); 595 } 596 } 597 598 } // namespace chrome_frame_test 599