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 string16 GetProfileIdFromPath(const base::FilePath& profile_path) {
     52   // Return empty string if profile_path is empty
     53   if (profile_path.empty())
     54     return 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 string16();
     64   }
     65 
     66   // Get joined basenames of user data dir and profile.
     67   string16 basenames = profile_path.DirName().BaseName().value() +
     68       L"." + profile_path.BaseName().value();
     69 
     70   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 string16 GetAppListAppName() {
     85   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
     86   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 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   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   if (!BrowserDistribution::GetDistribution()->CanSetAsDefault())
    196     return SET_DEFAULT_NOT_ALLOWED;
    197 
    198   if (ShellUtil::CanMakeChromeDefaultUnattended())
    199     return SET_DEFAULT_UNATTENDED;
    200   else
    201     return SET_DEFAULT_INTERACTIVE;
    202 }
    203 
    204 bool ShellIntegration::SetAsDefaultBrowser() {
    205   base::FilePath chrome_exe;
    206   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    207     LOG(ERROR) << "Error getting app exe path";
    208     return false;
    209   }
    210 
    211   // From UI currently we only allow setting default browser for current user.
    212   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    213   if (!ShellUtil::MakeChromeDefault(dist, ShellUtil::CURRENT_USER,
    214                                     chrome_exe.value(), true)) {
    215     LOG(ERROR) << "Chrome could not be set as default browser.";
    216     return false;
    217   }
    218 
    219   VLOG(1) << "Chrome registered as default browser.";
    220   return true;
    221 }
    222 
    223 bool ShellIntegration::SetAsDefaultProtocolClient(const std::string& protocol) {
    224   if (protocol.empty())
    225     return false;
    226 
    227   base::FilePath chrome_exe;
    228   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    229     LOG(ERROR) << "Error getting app exe path";
    230     return false;
    231   }
    232 
    233   string16 wprotocol(UTF8ToUTF16(protocol));
    234   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    235   if (!ShellUtil::MakeChromeDefaultProtocolClient(dist, chrome_exe.value(),
    236         wprotocol)) {
    237     LOG(ERROR) << "Chrome could not be set as default handler for "
    238                << protocol << ".";
    239     return false;
    240   }
    241 
    242   VLOG(1) << "Chrome registered as default handler for " << protocol << ".";
    243   return true;
    244 }
    245 
    246 bool ShellIntegration::SetAsDefaultBrowserInteractive() {
    247   base::FilePath chrome_exe;
    248   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    249     NOTREACHED() << "Error getting app exe path";
    250     return false;
    251   }
    252 
    253   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    254   if (!ShellUtil::ShowMakeChromeDefaultSystemUI(dist, chrome_exe.value())) {
    255     LOG(ERROR) << "Failed to launch the set-default-browser Windows UI.";
    256     return false;
    257   }
    258 
    259   VLOG(1) << "Set-default-browser Windows UI completed.";
    260   return true;
    261 }
    262 
    263 bool ShellIntegration::SetAsDefaultProtocolClientInteractive(
    264     const std::string& protocol) {
    265   base::FilePath chrome_exe;
    266   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    267     NOTREACHED() << "Error getting app exe path";
    268     return false;
    269   }
    270 
    271   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    272   string16 wprotocol(UTF8ToUTF16(protocol));
    273   if (!ShellUtil::ShowMakeChromeDefaultProtocolClientSystemUI(
    274           dist, chrome_exe.value(), wprotocol)) {
    275     LOG(ERROR) << "Failed to launch the set-default-client Windows UI.";
    276     return false;
    277   }
    278 
    279   VLOG(1) << "Set-default-client Windows UI completed.";
    280   return true;
    281 }
    282 
    283 ShellIntegration::DefaultWebClientState ShellIntegration::GetDefaultBrowser() {
    284   return GetDefaultWebClientStateFromShellUtilDefaultState(
    285       ShellUtil::GetChromeDefaultState());
    286 }
    287 
    288 ShellIntegration::DefaultWebClientState
    289     ShellIntegration::IsDefaultProtocolClient(const std::string& protocol) {
    290   return GetDefaultWebClientStateFromShellUtilDefaultState(
    291       ShellUtil::GetChromeDefaultProtocolClientState(UTF8ToUTF16(protocol)));
    292 }
    293 
    294 std::string ShellIntegration::GetApplicationForProtocol(const GURL& url) {
    295   // TODO(calamity): this will be implemented when external_protocol_dialog is
    296   // refactored on windows.
    297   NOTREACHED();
    298   return std::string();
    299 }
    300 
    301 // There is no reliable way to say which browser is default on a machine (each
    302 // browser can have some of the protocols/shortcuts). So we look for only HTTP
    303 // protocol handler. Even this handler is located at different places in
    304 // registry on XP and Vista:
    305 // - HKCR\http\shell\open\command (XP)
    306 // - HKCU\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\
    307 //   http\UserChoice (Vista)
    308 // This method checks if Firefox is defualt browser by checking these
    309 // locations and returns true if Firefox traces are found there. In case of
    310 // error (or if Firefox is not found)it returns the default value which
    311 // is false.
    312 bool ShellIntegration::IsFirefoxDefaultBrowser() {
    313   bool ff_default = false;
    314   if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
    315     string16 app_cmd;
    316     base::win::RegKey key(HKEY_CURRENT_USER,
    317                           ShellUtil::kRegVistaUrlPrefs, KEY_READ);
    318     if (key.Valid() && (key.ReadValue(L"Progid", &app_cmd) == ERROR_SUCCESS) &&
    319         app_cmd == L"FirefoxURL")
    320       ff_default = true;
    321   } else {
    322     string16 key_path(L"http");
    323     key_path.append(ShellUtil::kRegShellOpen);
    324     base::win::RegKey key(HKEY_CLASSES_ROOT, key_path.c_str(), KEY_READ);
    325     string16 app_cmd;
    326     if (key.Valid() && (key.ReadValue(L"", &app_cmd) == ERROR_SUCCESS) &&
    327         string16::npos != StringToLowerASCII(app_cmd).find(L"firefox"))
    328       ff_default = true;
    329   }
    330   return ff_default;
    331 }
    332 
    333 string16 ShellIntegration::GetAppModelIdForProfile(
    334     const string16& app_name,
    335     const base::FilePath& profile_path) {
    336   std::vector<string16> components;
    337   components.push_back(app_name);
    338   const string16 profile_id(GetProfileIdFromPath(profile_path));
    339   if (!profile_id.empty())
    340     components.push_back(profile_id);
    341   return ShellUtil::BuildAppModelId(components);
    342 }
    343 
    344 string16 ShellIntegration::GetChromiumModelIdForProfile(
    345     const base::FilePath& profile_path) {
    346   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    347   base::FilePath chrome_exe;
    348   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    349     NOTREACHED();
    350     return dist->GetBaseAppId();
    351   }
    352   return GetAppModelIdForProfile(
    353       ShellUtil::GetBrowserModelId(
    354            dist, InstallUtil::IsPerUserInstall(chrome_exe.value().c_str())),
    355       profile_path);
    356 }
    357 
    358 string16 ShellIntegration::GetAppListAppModelIdForProfile(
    359     const base::FilePath& profile_path) {
    360   return ShellIntegration::GetAppModelIdForProfile(
    361       GetAppListAppName(), profile_path);
    362 }
    363 
    364 string16 ShellIntegration::GetChromiumIconLocation() {
    365   // Determine the path to chrome.exe. If we can't determine what that is,
    366   // we have bigger fish to fry...
    367   base::FilePath chrome_exe;
    368   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    369     NOTREACHED();
    370     return string16();
    371   }
    372 
    373   return ShellUtil::FormatIconLocation(
    374       chrome_exe.value(),
    375       BrowserDistribution::GetDistribution()->GetIconIndex());
    376 }
    377 
    378 void ShellIntegration::MigrateChromiumShortcuts() {
    379   if (base::win::GetVersion() < base::win::VERSION_WIN7)
    380     return;
    381 
    382   // This needs to happen eventually (e.g. so that the appid is fixed and the
    383   // run-time Chrome icon is merged with the taskbar shortcut), but this is not
    384   // urgent and shouldn't delay Chrome startup.
    385   static const int64 kMigrateChromiumShortcutsDelaySeconds = 15;
    386   BrowserThread::PostDelayedTask(
    387       BrowserThread::FILE, FROM_HERE,
    388       base::Bind(&MigrateChromiumShortcutsCallback),
    389       base::TimeDelta::FromSeconds(kMigrateChromiumShortcutsDelaySeconds));
    390 }
    391 
    392 int ShellIntegration::MigrateShortcutsInPathInternal(
    393     const base::FilePath& chrome_exe,
    394     const base::FilePath& path,
    395     bool check_dual_mode) {
    396   DCHECK(base::win::GetVersion() >= base::win::VERSION_WIN7);
    397 
    398   // Enumerate all pinned shortcuts in the given path directly.
    399   base::FileEnumerator shortcuts_enum(
    400       path, false,  // not recursive
    401       base::FileEnumerator::FILES, FILE_PATH_LITERAL("*.lnk"));
    402 
    403   bool is_per_user_install =
    404       InstallUtil::IsPerUserInstall(chrome_exe.value().c_str());
    405 
    406   int shortcuts_migrated = 0;
    407   base::FilePath target_path;
    408   string16 arguments;
    409   base::win::ScopedPropVariant propvariant;
    410   for (base::FilePath shortcut = shortcuts_enum.Next(); !shortcut.empty();
    411        shortcut = shortcuts_enum.Next()) {
    412     // TODO(gab): Use ProgramCompare instead of comparing FilePaths below once
    413     // it is fixed to work with FilePaths with spaces.
    414     if (!base::win::ResolveShortcut(shortcut, &target_path, &arguments) ||
    415         chrome_exe != target_path) {
    416       continue;
    417     }
    418     CommandLine command_line(CommandLine::FromString(base::StringPrintf(
    419         L"\"%ls\" %ls", target_path.value().c_str(), arguments.c_str())));
    420 
    421     // Get the expected AppId for this Chrome shortcut.
    422     string16 expected_app_id(
    423         GetExpectedAppId(command_line, is_per_user_install));
    424     if (expected_app_id.empty())
    425       continue;
    426 
    427     // Load the shortcut.
    428     base::win::ScopedComPtr<IShellLink> shell_link;
    429     base::win::ScopedComPtr<IPersistFile> persist_file;
    430     if (FAILED(shell_link.CreateInstance(CLSID_ShellLink, NULL,
    431                                          CLSCTX_INPROC_SERVER)) ||
    432         FAILED(persist_file.QueryFrom(shell_link)) ||
    433         FAILED(persist_file->Load(shortcut.value().c_str(), STGM_READ))) {
    434       DLOG(WARNING) << "Failed loading shortcut at " << shortcut.value();
    435       continue;
    436     }
    437 
    438     // Any properties that need to be updated on the shortcut will be stored in
    439     // |updated_properties|.
    440     base::win::ShortcutProperties updated_properties;
    441 
    442     // Validate the existing app id for the shortcut.
    443     base::win::ScopedComPtr<IPropertyStore> property_store;
    444     propvariant.Reset();
    445     if (FAILED(property_store.QueryFrom(shell_link)) ||
    446         property_store->GetValue(PKEY_AppUserModel_ID,
    447                                  propvariant.Receive()) != S_OK) {
    448       // When in doubt, prefer not updating the shortcut.
    449       NOTREACHED();
    450       continue;
    451     } else {
    452       switch (propvariant.get().vt) {
    453         case VT_EMPTY:
    454           // If there is no app_id set, set our app_id if one is expected.
    455           if (!expected_app_id.empty())
    456             updated_properties.set_app_id(expected_app_id);
    457           break;
    458         case VT_LPWSTR:
    459           if (expected_app_id != string16(propvariant.get().pwszVal))
    460             updated_properties.set_app_id(expected_app_id);
    461           break;
    462         default:
    463           NOTREACHED();
    464           continue;
    465       }
    466     }
    467 
    468     // Only set dual mode if the expected app id is the default app id.
    469     BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    470     string16 default_chromium_model_id(
    471         ShellUtil::GetBrowserModelId(dist, is_per_user_install));
    472     if (check_dual_mode && expected_app_id == default_chromium_model_id) {
    473       propvariant.Reset();
    474       if (property_store->GetValue(PKEY_AppUserModel_IsDualMode,
    475                                    propvariant.Receive()) != S_OK) {
    476         // When in doubt, prefer to not update the shortcut.
    477         NOTREACHED();
    478         continue;
    479       } else {
    480         switch (propvariant.get().vt) {
    481           case VT_EMPTY:
    482             // If dual_mode is not set at all, make sure it gets set to true.
    483             updated_properties.set_dual_mode(true);
    484             break;
    485           case VT_BOOL:
    486             // If it is set to false, make sure it gets set to true as well.
    487             if (!propvariant.get().boolVal)
    488               updated_properties.set_dual_mode(true);
    489             break;
    490           default:
    491             NOTREACHED();
    492             continue;
    493         }
    494       }
    495     }
    496 
    497     persist_file.Release();
    498     shell_link.Release();
    499 
    500     // Update the shortcut if some of its properties need to be updated.
    501     if (updated_properties.options &&
    502         base::win::CreateOrUpdateShortcutLink(
    503             shortcut, updated_properties,
    504             base::win::SHORTCUT_UPDATE_EXISTING)) {
    505       ++shortcuts_migrated;
    506     }
    507   }
    508   return shortcuts_migrated;
    509 }
    510 
    511 base::FilePath ShellIntegration::GetStartMenuShortcut(
    512     const base::FilePath& chrome_exe) {
    513   static const int kFolderIds[] = {
    514     base::DIR_COMMON_START_MENU,
    515     base::DIR_START_MENU,
    516   };
    517   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    518   string16 shortcut_name(dist->GetAppShortCutName());
    519   base::FilePath shortcut;
    520 
    521   // Check both the common and the per-user Start Menu folders for system-level
    522   // installs.
    523   size_t folder =
    524       InstallUtil::IsPerUserInstall(chrome_exe.value().c_str()) ? 1 : 0;
    525   for (; folder < arraysize(kFolderIds); ++folder) {
    526     if (!PathService::Get(kFolderIds[folder], &shortcut)) {
    527       NOTREACHED();
    528       continue;
    529     }
    530 
    531     shortcut = shortcut.Append(shortcut_name).Append(shortcut_name +
    532                                                      installer::kLnkExt);
    533     if (base::PathExists(shortcut))
    534       return shortcut;
    535   }
    536 
    537   return base::FilePath();
    538 }
    539