Home | History | Annotate | Download | only in browser
      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/browser/shell_integration.h"
      6 
      7 #include <windows.h>
      8 #include <shobjidl.h>
      9 #include <propkey.h>
     10 
     11 #include "base/bind.h"
     12 #include "base/command_line.h"
     13 #include "base/file_util.h"
     14 #include "base/files/file_enumerator.h"
     15 #include "base/message_loop/message_loop.h"
     16 #include "base/path_service.h"
     17 #include "base/strings/string_number_conversions.h"
     18 #include "base/strings/string_util.h"
     19 #include "base/strings/stringprintf.h"
     20 #include "base/strings/utf_string_conversions.h"
     21 #include "base/win/registry.h"
     22 #include "base/win/scoped_comptr.h"
     23 #include "base/win/scoped_propvariant.h"
     24 #include "base/win/shortcut.h"
     25 #include "base/win/windows_version.h"
     26 #include "chrome/browser/policy/policy_path_parser.h"
     27 #include "chrome/browser/web_applications/web_app.h"
     28 #include "chrome/common/chrome_constants.h"
     29 #include "chrome/common/chrome_paths_internal.h"
     30 #include "chrome/common/chrome_switches.h"
     31 #include "chrome/installer/setup/setup_util.h"
     32 #include "chrome/installer/util/browser_distribution.h"
     33 #include "chrome/installer/util/create_reg_key_work_item.h"
     34 #include "chrome/installer/util/install_util.h"
     35 #include "chrome/installer/util/set_reg_value_work_item.h"
     36 #include "chrome/installer/util/shell_util.h"
     37 #include "chrome/installer/util/util_constants.h"
     38 #include "chrome/installer/util/work_item.h"
     39 #include "chrome/installer/util/work_item_list.h"
     40 #include "content/public/browser/browser_thread.h"
     41 
     42 using content::BrowserThread;
     43 
     44 namespace {
     45 
     46 const wchar_t kAppListAppNameSuffix[] = L"AppList";
     47 
     48 // Helper function for ShellIntegration::GetAppId to generates profile id
     49 // from profile path. "profile_id" is composed of sanitized basenames of
     50 // user data dir and profile dir joined by a ".".
     51 base::string16 GetProfileIdFromPath(const base::FilePath& profile_path) {
     52   // Return empty string if profile_path is empty
     53   if (profile_path.empty())
     54     return base::string16();
     55 
     56   base::FilePath default_user_data_dir;
     57   // Return empty string if profile_path is in default user data
     58   // dir and is the default profile.
     59   if (chrome::GetDefaultUserDataDirectory(&default_user_data_dir) &&
     60       profile_path.DirName() == default_user_data_dir &&
     61       profile_path.BaseName().value() ==
     62           ASCIIToUTF16(chrome::kInitialProfile)) {
     63     return base::string16();
     64   }
     65 
     66   // Get joined basenames of user data dir and profile.
     67   base::string16 basenames = profile_path.DirName().BaseName().value() +
     68       L"." + profile_path.BaseName().value();
     69 
     70   base::string16 profile_id;
     71   profile_id.reserve(basenames.size());
     72 
     73   // Generate profile_id from sanitized basenames.
     74   for (size_t i = 0; i < basenames.length(); ++i) {
     75     if (IsAsciiAlpha(basenames[i]) ||
     76         IsAsciiDigit(basenames[i]) ||
     77         basenames[i] == L'.')
     78       profile_id += basenames[i];
     79   }
     80 
     81   return profile_id;
     82 }
     83 
     84 base::string16 GetAppListAppName() {
     85   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
     86   base::string16 app_name(dist->GetBaseAppId());
     87   app_name.append(kAppListAppNameSuffix);
     88   return app_name;
     89 }
     90 
     91 // Gets expected app id for given Chrome (based on |command_line| and
     92 // |is_per_user_install|).
     93 base::string16 GetExpectedAppId(const CommandLine& command_line,
     94                                 bool is_per_user_install) {
     95   base::FilePath user_data_dir;
     96   if (command_line.HasSwitch(switches::kUserDataDir))
     97     user_data_dir = command_line.GetSwitchValuePath(switches::kUserDataDir);
     98   else
     99     chrome::GetDefaultUserDataDirectory(&user_data_dir);
    100   // Adjust with any policy that overrides any other way to set the path.
    101   policy::path_parser::CheckUserDataDirPolicy(&user_data_dir);
    102   DCHECK(!user_data_dir.empty());
    103 
    104   base::FilePath profile_subdir;
    105   if (command_line.HasSwitch(switches::kProfileDirectory)) {
    106     profile_subdir =
    107         command_line.GetSwitchValuePath(switches::kProfileDirectory);
    108   } else {
    109     profile_subdir = base::FilePath(ASCIIToUTF16(chrome::kInitialProfile));
    110   }
    111   DCHECK(!profile_subdir.empty());
    112 
    113   base::FilePath profile_path = user_data_dir.Append(profile_subdir);
    114   base::string16 app_name;
    115   if (command_line.HasSwitch(switches::kApp)) {
    116     app_name = UTF8ToUTF16(web_app::GenerateApplicationNameFromURL(
    117         GURL(command_line.GetSwitchValueASCII(switches::kApp))));
    118   } else if (command_line.HasSwitch(switches::kAppId)) {
    119     app_name = UTF8ToUTF16(web_app::GenerateApplicationNameFromExtensionId(
    120         command_line.GetSwitchValueASCII(switches::kAppId)));
    121   } else if (command_line.HasSwitch(switches::kShowAppList)) {
    122     app_name = GetAppListAppName();
    123   } else {
    124     BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    125     app_name = ShellUtil::GetBrowserModelId(dist, is_per_user_install);
    126   }
    127   DCHECK(!app_name.empty());
    128 
    129   return ShellIntegration::GetAppModelIdForProfile(app_name, profile_path);
    130 }
    131 
    132 void MigrateChromiumShortcutsCallback() {
    133   // This should run on the file thread.
    134   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    135 
    136   // Get full path of chrome.
    137   base::FilePath chrome_exe;
    138   if (!PathService::Get(base::FILE_EXE, &chrome_exe))
    139     return;
    140 
    141   // Locations to check for shortcuts migration.
    142   static const struct {
    143     int location_id;
    144     const wchar_t* sub_dir;
    145   } kLocations[] = {
    146     {
    147       base::DIR_TASKBAR_PINS,
    148       NULL
    149     }, {
    150       base::DIR_USER_DESKTOP,
    151       NULL
    152     }, {
    153       base::DIR_START_MENU,
    154       NULL
    155     }, {
    156       base::DIR_APP_DATA,
    157       L"Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\StartMenu"
    158     }
    159   };
    160 
    161   for (int i = 0; i < arraysize(kLocations); ++i) {
    162     base::FilePath path;
    163     if (!PathService::Get(kLocations[i].location_id, &path)) {
    164       NOTREACHED();
    165       continue;
    166     }
    167 
    168     if (kLocations[i].sub_dir)
    169       path = path.Append(kLocations[i].sub_dir);
    170 
    171     bool check_dual_mode = (kLocations[i].location_id == base::DIR_START_MENU);
    172     ShellIntegration::MigrateShortcutsInPathInternal(chrome_exe, path,
    173                                                      check_dual_mode);
    174   }
    175 }
    176 
    177 ShellIntegration::DefaultWebClientState
    178     GetDefaultWebClientStateFromShellUtilDefaultState(
    179         ShellUtil::DefaultState default_state) {
    180   switch (default_state) {
    181     case ShellUtil::NOT_DEFAULT:
    182       return ShellIntegration::NOT_DEFAULT;
    183     case ShellUtil::IS_DEFAULT:
    184       return ShellIntegration::IS_DEFAULT;
    185     default:
    186       DCHECK_EQ(ShellUtil::UNKNOWN_DEFAULT, default_state);
    187       return ShellIntegration::UNKNOWN_DEFAULT;
    188   }
    189 }
    190 
    191 }  // namespace
    192 
    193 ShellIntegration::DefaultWebClientSetPermission
    194     ShellIntegration::CanSetAsDefaultBrowser() {
    195   BrowserDistribution* distribution = BrowserDistribution::GetDistribution();
    196   if (distribution->GetDefaultBrowserControlPolicy() !=
    197           BrowserDistribution::DEFAULT_BROWSER_FULL_CONTROL)
    198     return SET_DEFAULT_NOT_ALLOWED;
    199 
    200   if (ShellUtil::CanMakeChromeDefaultUnattended())
    201     return SET_DEFAULT_UNATTENDED;
    202   else
    203     return SET_DEFAULT_INTERACTIVE;
    204 }
    205 
    206 bool ShellIntegration::SetAsDefaultBrowser() {
    207   base::FilePath chrome_exe;
    208   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    209     LOG(ERROR) << "Error getting app exe path";
    210     return false;
    211   }
    212 
    213   // From UI currently we only allow setting default browser for current user.
    214   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    215   if (!ShellUtil::MakeChromeDefault(dist, ShellUtil::CURRENT_USER,
    216                                     chrome_exe.value(), true)) {
    217     LOG(ERROR) << "Chrome could not be set as default browser.";
    218     return false;
    219   }
    220 
    221   VLOG(1) << "Chrome registered as default browser.";
    222   return true;
    223 }
    224 
    225 bool ShellIntegration::SetAsDefaultProtocolClient(const std::string& protocol) {
    226   if (protocol.empty())
    227     return false;
    228 
    229   base::FilePath chrome_exe;
    230   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    231     LOG(ERROR) << "Error getting app exe path";
    232     return false;
    233   }
    234 
    235   base::string16 wprotocol(UTF8ToUTF16(protocol));
    236   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    237   if (!ShellUtil::MakeChromeDefaultProtocolClient(dist, chrome_exe.value(),
    238         wprotocol)) {
    239     LOG(ERROR) << "Chrome could not be set as default handler for "
    240                << protocol << ".";
    241     return false;
    242   }
    243 
    244   VLOG(1) << "Chrome registered as default handler for " << protocol << ".";
    245   return true;
    246 }
    247 
    248 bool ShellIntegration::SetAsDefaultBrowserInteractive() {
    249   base::FilePath chrome_exe;
    250   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    251     NOTREACHED() << "Error getting app exe path";
    252     return false;
    253   }
    254 
    255   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    256   if (!ShellUtil::ShowMakeChromeDefaultSystemUI(dist, chrome_exe.value())) {
    257     LOG(ERROR) << "Failed to launch the set-default-browser Windows UI.";
    258     return false;
    259   }
    260 
    261   VLOG(1) << "Set-default-browser Windows UI completed.";
    262   return true;
    263 }
    264 
    265 bool ShellIntegration::SetAsDefaultProtocolClientInteractive(
    266     const std::string& protocol) {
    267   base::FilePath chrome_exe;
    268   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    269     NOTREACHED() << "Error getting app exe path";
    270     return false;
    271   }
    272 
    273   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    274   base::string16 wprotocol(UTF8ToUTF16(protocol));
    275   if (!ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(
    276           dist, chrome_exe.value(), wprotocol)) {
    277     LOG(ERROR) << "Failed to launch the set-default-client Windows UI.";
    278     return false;
    279   }
    280 
    281   VLOG(1) << "Set-default-client Windows UI completed.";
    282   return true;
    283 }
    284 
    285 ShellIntegration::DefaultWebClientState ShellIntegration::GetDefaultBrowser() {
    286   return GetDefaultWebClientStateFromShellUtilDefaultState(
    287       ShellUtil::GetChromeDefaultState());
    288 }
    289 
    290 ShellIntegration::DefaultWebClientState
    291     ShellIntegration::IsDefaultProtocolClient(const std::string& protocol) {
    292   return GetDefaultWebClientStateFromShellUtilDefaultState(
    293       ShellUtil::GetChromeDefaultProtocolClientState(UTF8ToUTF16(protocol)));
    294 }
    295 
    296 std::string ShellIntegration::GetApplicationForProtocol(const GURL& url) {
    297   // TODO(calamity): this will be implemented when external_protocol_dialog is
    298   // refactored on windows.
    299   NOTREACHED();
    300   return std::string();
    301 }
    302 
    303 // There is no reliable way to say which browser is default on a machine (each
    304 // browser can have some of the protocols/shortcuts). So we look for only HTTP
    305 // protocol handler. Even this handler is located at different places in
    306 // registry on XP and Vista:
    307 // - HKCR\http\shell\open\command (XP)
    308 // - HKCU\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\
    309 //   http\UserChoice (Vista)
    310 // This method checks if Firefox is defualt browser by checking these
    311 // locations and returns true if Firefox traces are found there. In case of
    312 // error (or if Firefox is not found)it returns the default value which
    313 // is false.
    314 bool ShellIntegration::IsFirefoxDefaultBrowser() {
    315   bool ff_default = false;
    316   if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
    317     base::string16 app_cmd;
    318     base::win::RegKey key(HKEY_CURRENT_USER,
    319                           ShellUtil::kRegVistaUrlPrefs, KEY_READ);
    320     if (key.Valid() && (key.ReadValue(L"Progid", &app_cmd) == ERROR_SUCCESS) &&
    321         app_cmd == L"FirefoxURL")
    322       ff_default = true;
    323   } else {
    324     base::string16 key_path(L"http");
    325     key_path.append(ShellUtil::kRegShellOpen);
    326     base::win::RegKey key(HKEY_CLASSES_ROOT, key_path.c_str(), KEY_READ);
    327     base::string16 app_cmd;
    328     if (key.Valid() && (key.ReadValue(L"", &app_cmd) == ERROR_SUCCESS) &&
    329         base::string16::npos != StringToLowerASCII(app_cmd).find(L"firefox"))
    330       ff_default = true;
    331   }
    332   return ff_default;
    333 }
    334 
    335 base::string16 ShellIntegration::GetAppModelIdForProfile(
    336     const base::string16& app_name,
    337     const base::FilePath& profile_path) {
    338   std::vector<base::string16> components;
    339   components.push_back(app_name);
    340   const base::string16 profile_id(GetProfileIdFromPath(profile_path));
    341   if (!profile_id.empty())
    342     components.push_back(profile_id);
    343   return ShellUtil::BuildAppModelId(components);
    344 }
    345 
    346 base::string16 ShellIntegration::GetChromiumModelIdForProfile(
    347     const base::FilePath& profile_path) {
    348   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    349   base::FilePath chrome_exe;
    350   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    351     NOTREACHED();
    352     return dist->GetBaseAppId();
    353   }
    354   return GetAppModelIdForProfile(
    355       ShellUtil::GetBrowserModelId(
    356            dist, InstallUtil::IsPerUserInstall(chrome_exe.value().c_str())),
    357       profile_path);
    358 }
    359 
    360 base::string16 ShellIntegration::GetAppListAppModelIdForProfile(
    361     const base::FilePath& profile_path) {
    362   return ShellIntegration::GetAppModelIdForProfile(
    363       GetAppListAppName(), profile_path);
    364 }
    365 
    366 void ShellIntegration::MigrateChromiumShortcuts() {
    367   if (base::win::GetVersion() < base::win::VERSION_WIN7)
    368     return;
    369 
    370   // This needs to happen eventually (e.g. so that the appid is fixed and the
    371   // run-time Chrome icon is merged with the taskbar shortcut), but this is not
    372   // urgent and shouldn't delay Chrome startup.
    373   static const int64 kMigrateChromiumShortcutsDelaySeconds = 15;
    374   BrowserThread::PostDelayedTask(
    375       BrowserThread::FILE, FROM_HERE,
    376       base::Bind(&MigrateChromiumShortcutsCallback),
    377       base::TimeDelta::FromSeconds(kMigrateChromiumShortcutsDelaySeconds));
    378 }
    379 
    380 int ShellIntegration::MigrateShortcutsInPathInternal(
    381     const base::FilePath& chrome_exe,
    382     const base::FilePath& path,
    383     bool check_dual_mode) {
    384   DCHECK(base::win::GetVersion() >= base::win::VERSION_WIN7);
    385 
    386   // Enumerate all pinned shortcuts in the given path directly.
    387   base::FileEnumerator shortcuts_enum(
    388       path, false,  // not recursive
    389       base::FileEnumerator::FILES, FILE_PATH_LITERAL("*.lnk"));
    390 
    391   bool is_per_user_install =
    392       InstallUtil::IsPerUserInstall(chrome_exe.value().c_str());
    393 
    394   int shortcuts_migrated = 0;
    395   base::FilePath target_path;
    396   base::string16 arguments;
    397   base::win::ScopedPropVariant propvariant;
    398   for (base::FilePath shortcut = shortcuts_enum.Next(); !shortcut.empty();
    399        shortcut = shortcuts_enum.Next()) {
    400     // TODO(gab): Use ProgramCompare instead of comparing FilePaths below once
    401     // it is fixed to work with FilePaths with spaces.
    402     if (!base::win::ResolveShortcut(shortcut, &target_path, &arguments) ||
    403         chrome_exe != target_path) {
    404       continue;
    405     }
    406     CommandLine command_line(CommandLine::FromString(base::StringPrintf(
    407         L"\"%ls\" %ls", target_path.value().c_str(), arguments.c_str())));
    408 
    409     // Get the expected AppId for this Chrome shortcut.
    410     base::string16 expected_app_id(
    411         GetExpectedAppId(command_line, is_per_user_install));
    412     if (expected_app_id.empty())
    413       continue;
    414 
    415     // Load the shortcut.
    416     base::win::ScopedComPtr<IShellLink> shell_link;
    417     base::win::ScopedComPtr<IPersistFile> persist_file;
    418     if (FAILED(shell_link.CreateInstance(CLSID_ShellLink, NULL,
    419                                          CLSCTX_INPROC_SERVER)) ||
    420         FAILED(persist_file.QueryFrom(shell_link)) ||
    421         FAILED(persist_file->Load(shortcut.value().c_str(), STGM_READ))) {
    422       DLOG(WARNING) << "Failed loading shortcut at " << shortcut.value();
    423       continue;
    424     }
    425 
    426     // Any properties that need to be updated on the shortcut will be stored in
    427     // |updated_properties|.
    428     base::win::ShortcutProperties updated_properties;
    429 
    430     // Validate the existing app id for the shortcut.
    431     base::win::ScopedComPtr<IPropertyStore> property_store;
    432     propvariant.Reset();
    433     if (FAILED(property_store.QueryFrom(shell_link)) ||
    434         property_store->GetValue(PKEY_AppUserModel_ID,
    435                                  propvariant.Receive()) != S_OK) {
    436       // When in doubt, prefer not updating the shortcut.
    437       NOTREACHED();
    438       continue;
    439     } else {
    440       switch (propvariant.get().vt) {
    441         case VT_EMPTY:
    442           // If there is no app_id set, set our app_id if one is expected.
    443           if (!expected_app_id.empty())
    444             updated_properties.set_app_id(expected_app_id);
    445           break;
    446         case VT_LPWSTR:
    447           if (expected_app_id != base::string16(propvariant.get().pwszVal))
    448             updated_properties.set_app_id(expected_app_id);
    449           break;
    450         default:
    451           NOTREACHED();
    452           continue;
    453       }
    454     }
    455 
    456     // Only set dual mode if the expected app id is the default app id.
    457     BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    458     base::string16 default_chromium_model_id(
    459         ShellUtil::GetBrowserModelId(dist, is_per_user_install));
    460     if (check_dual_mode && expected_app_id == default_chromium_model_id) {
    461       propvariant.Reset();
    462       if (property_store->GetValue(PKEY_AppUserModel_IsDualMode,
    463                                    propvariant.Receive()) != S_OK) {
    464         // When in doubt, prefer to not update the shortcut.
    465         NOTREACHED();
    466         continue;
    467       } else {
    468         switch (propvariant.get().vt) {
    469           case VT_EMPTY:
    470             // If dual_mode is not set at all, make sure it gets set to true.
    471             updated_properties.set_dual_mode(true);
    472             break;
    473           case VT_BOOL:
    474             // If it is set to false, make sure it gets set to true as well.
    475             if (!propvariant.get().boolVal)
    476               updated_properties.set_dual_mode(true);
    477             break;
    478           default:
    479             NOTREACHED();
    480             continue;
    481         }
    482       }
    483     }
    484 
    485     persist_file.Release();
    486     shell_link.Release();
    487 
    488     // Update the shortcut if some of its properties need to be updated.
    489     if (updated_properties.options &&
    490         base::win::CreateOrUpdateShortcutLink(
    491             shortcut, updated_properties,
    492             base::win::SHORTCUT_UPDATE_EXISTING)) {
    493       ++shortcuts_migrated;
    494     }
    495   }
    496   return shortcuts_migrated;
    497 }
    498 
    499 base::FilePath ShellIntegration::GetStartMenuShortcut(
    500     const base::FilePath& chrome_exe) {
    501   static const int kFolderIds[] = {
    502     base::DIR_COMMON_START_MENU,
    503     base::DIR_START_MENU,
    504   };
    505   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    506   base::string16 shortcut_name(
    507       dist->GetShortcutName(BrowserDistribution::SHORTCUT_CHROME));
    508   base::FilePath shortcut;
    509 
    510   // Check both the common and the per-user Start Menu folders for system-level
    511   // installs.
    512   size_t folder =
    513       InstallUtil::IsPerUserInstall(chrome_exe.value().c_str()) ? 1 : 0;
    514   for (; folder < arraysize(kFolderIds); ++folder) {
    515     if (!PathService::Get(kFolderIds[folder], &shortcut)) {
    516       NOTREACHED();
    517       continue;
    518     }
    519 
    520     shortcut = shortcut.Append(shortcut_name).Append(shortcut_name +
    521                                                      installer::kLnkExt);
    522     if (base::PathExists(shortcut))
    523       return shortcut;
    524   }
    525 
    526   return base::FilePath();
    527 }
    528