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