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 <vector>
8
9#include "base/files/file_path.h"
10#include "base/files/file_util.h"
11#include "base/files/scoped_temp_dir.h"
12#include "base/strings/string16.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/test/test_shortcut_win.h"
16#include "base/win/scoped_com_initializer.h"
17#include "base/win/windows_version.h"
18#include "chrome/browser/web_applications/web_app.h"
19#include "chrome/common/chrome_constants.h"
20#include "chrome/common/chrome_paths_internal.h"
21#include "chrome/installer/util/browser_distribution.h"
22#include "chrome/installer/util/shell_util.h"
23#include "chrome/installer/util/util_constants.h"
24#include "testing/gtest/include/gtest/gtest.h"
25
26namespace {
27
28struct ShortcutTestObject {
29  base::FilePath path;
30  base::win::ShortcutProperties properties;
31};
32
33class ShellIntegrationWinMigrateShortcutTest : public testing::Test {
34 protected:
35  virtual void SetUp() OVERRIDE {
36    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
37
38    // A path to a random target.
39    base::CreateTemporaryFileInDir(temp_dir_.path(), &other_target_);
40
41    // This doesn't need to actually have a base name of "chrome.exe".
42    base::CreateTemporaryFileInDir(temp_dir_.path(), &chrome_exe_);
43
44    chrome_app_id_ =
45        ShellUtil::GetBrowserModelId(BrowserDistribution::GetDistribution(),
46                                     true);
47
48    base::FilePath default_user_data_dir;
49    chrome::GetDefaultUserDataDirectory(&default_user_data_dir);
50    base::FilePath default_profile_path =
51        default_user_data_dir.AppendASCII(chrome::kInitialProfile);
52    app_list_app_id_ =
53        ShellIntegration::GetAppListAppModelIdForProfile(default_profile_path);
54    non_default_user_data_dir_ = base::FilePath(FILE_PATH_LITERAL("root"))
55        .Append(FILE_PATH_LITERAL("Non Default Data Dir"));
56    non_default_profile_ = L"NonDefault";
57    non_default_profile_chrome_app_id_ =
58        ShellIntegration::GetChromiumModelIdForProfile(
59        default_user_data_dir.Append(non_default_profile_));
60    non_default_user_data_dir_chrome_app_id_ =
61        ShellIntegration::GetChromiumModelIdForProfile(
62        non_default_user_data_dir_.AppendASCII(chrome::kInitialProfile));
63    non_default_user_data_dir_and_profile_chrome_app_id_ =
64        ShellIntegration::GetChromiumModelIdForProfile(
65        non_default_user_data_dir_.Append(non_default_profile_));
66
67
68    extension_id_ = L"chromiumexampleappidforunittests";
69    base::string16 app_name =
70        base::UTF8ToUTF16(web_app::GenerateApplicationNameFromExtensionId(
71        base::UTF16ToUTF8(extension_id_)));
72    extension_app_id_ =
73        ShellIntegration::GetAppModelIdForProfile(app_name,
74                                                  default_profile_path);
75    non_default_profile_extension_app_id_ =
76        ShellIntegration::GetAppModelIdForProfile(
77        app_name,
78        default_user_data_dir.Append(non_default_profile_));
79
80    CreateShortcuts();
81  }
82
83  // Creates a test shortcut corresponding to |shortcut_properties| and resets
84  // |shortcut_properties| after copying it to an internal structure for later
85  // verification.
86  void AddTestShortcutAndResetProperties(
87      base::win::ShortcutProperties* shortcut_properties) {
88    ShortcutTestObject shortcut_test_object;
89    base::FilePath shortcut_path =
90        temp_dir_.path().Append(L"Shortcut " +
91                                base::IntToString16(shortcuts_.size()) +
92                                installer::kLnkExt);
93    shortcut_test_object.path = shortcut_path;
94    shortcut_test_object.properties = *shortcut_properties;
95    shortcuts_.push_back(shortcut_test_object);
96    ASSERT_TRUE(base::win::CreateOrUpdateShortcutLink(
97        shortcut_path, *shortcut_properties,
98        base::win::SHORTCUT_CREATE_ALWAYS));
99    shortcut_properties->options = 0U;
100  }
101
102  void CreateShortcuts() {
103    // A temporary object to pass properties to
104    // AddTestShortcutAndResetProperties().
105    base::win::ShortcutProperties temp_properties;
106
107    // Shortcut 0 doesn't point to chrome.exe and thus should never be migrated.
108    temp_properties.set_target(other_target_);
109    temp_properties.set_app_id(L"Dumbo");
110    ASSERT_NO_FATAL_FAILURE(
111        AddTestShortcutAndResetProperties(&temp_properties));
112
113    // Shortcut 1 points to chrome.exe and thus should be migrated.
114    temp_properties.set_target(chrome_exe_);
115    temp_properties.set_app_id(L"Dumbo");
116    temp_properties.set_dual_mode(false);
117    ASSERT_NO_FATAL_FAILURE(
118        AddTestShortcutAndResetProperties(&temp_properties));
119
120    // Shortcut 2 points to chrome.exe, but already has the right appid and thus
121    // should only be migrated if dual_mode is desired.
122    temp_properties.set_target(chrome_exe_);
123    temp_properties.set_app_id(chrome_app_id_);
124    ASSERT_NO_FATAL_FAILURE(
125        AddTestShortcutAndResetProperties(&temp_properties));
126
127    // Shortcut 3 is like shortcut 1, but it's appid is a prefix of the expected
128    // appid instead of being totally different.
129    base::string16 chrome_app_id_is_prefix(chrome_app_id_);
130    chrome_app_id_is_prefix.push_back(L'1');
131    temp_properties.set_target(chrome_exe_);
132    temp_properties.set_app_id(chrome_app_id_is_prefix);
133    ASSERT_NO_FATAL_FAILURE(
134        AddTestShortcutAndResetProperties(&temp_properties));
135
136    // Shortcut 4 is like shortcut 1, but it's appid is of the same size as the
137    // expected appid.
138    base::string16 same_size_as_chrome_app_id(L'1', chrome_app_id_.size());
139    temp_properties.set_target(chrome_exe_);
140    temp_properties.set_app_id(same_size_as_chrome_app_id);
141    ASSERT_NO_FATAL_FAILURE(
142        AddTestShortcutAndResetProperties(&temp_properties));
143
144    // Shortcut 5 doesn't have an app_id, nor is dual_mode even set; they should
145    // be set as expected upon migration.
146    temp_properties.set_target(chrome_exe_);
147    ASSERT_NO_FATAL_FAILURE(
148        AddTestShortcutAndResetProperties(&temp_properties));
149
150    // Shortcut 6 has a non-default profile directory and so should get a non-
151    // default app id.
152    temp_properties.set_target(chrome_exe_);
153    temp_properties.set_app_id(L"Dumbo");
154    temp_properties.set_arguments(
155        L"--profile-directory=" + non_default_profile_);
156    ASSERT_NO_FATAL_FAILURE(
157        AddTestShortcutAndResetProperties(&temp_properties));
158
159    // Shortcut 7 has a non-default user data directory and so should get a non-
160    // default app id.
161    temp_properties.set_target(chrome_exe_);
162    temp_properties.set_app_id(L"Dumbo");
163    temp_properties.set_arguments(
164        L"--user-data-dir=\"" + non_default_user_data_dir_.value() + L"\"");
165    ASSERT_NO_FATAL_FAILURE(
166        AddTestShortcutAndResetProperties(&temp_properties));
167
168    // Shortcut 8 has a non-default user data directory as well as a non-default
169    // profile directory and so should get a non-default app id.
170    temp_properties.set_target(chrome_exe_);
171    temp_properties.set_app_id(L"Dumbo");
172    temp_properties.set_arguments(
173        L"--user-data-dir=\"" + non_default_user_data_dir_.value() + L"\" " +
174        L"--profile-directory=" + non_default_profile_);
175    ASSERT_NO_FATAL_FAILURE(
176        AddTestShortcutAndResetProperties(&temp_properties));
177
178    // Shortcut 9 is a shortcut to an app and should get an app id for that app
179    // rather than the chrome app id.
180    temp_properties.set_target(chrome_exe_);
181    temp_properties.set_app_id(L"Dumbo");
182    temp_properties.set_arguments(
183        L"--app-id=" + extension_id_);
184    ASSERT_NO_FATAL_FAILURE(
185        AddTestShortcutAndResetProperties(&temp_properties));
186
187    // Shortcut 10 is a shortcut to an app with a non-default profile and should
188    // get an app id for that app with a non-default app id rather than the
189    // chrome app id.
190    temp_properties.set_target(chrome_exe_);
191    temp_properties.set_app_id(L"Dumbo");
192    temp_properties.set_arguments(
193        L"--app-id=" + extension_id_ +
194        L" --profile-directory=" + non_default_profile_);
195    ASSERT_NO_FATAL_FAILURE(
196        AddTestShortcutAndResetProperties(&temp_properties));
197  }
198
199  base::win::ScopedCOMInitializer com_initializer_;
200
201  base::ScopedTempDir temp_dir_;
202
203  // Test shortcuts.
204  std::vector<ShortcutTestObject> shortcuts_;
205
206  // The path to a fake chrome.exe.
207  base::FilePath chrome_exe_;
208
209  // The path to a random target.
210  base::FilePath other_target_;
211
212  // Chrome's AppUserModelId.
213  base::string16 chrome_app_id_;
214
215  // A profile that isn't the Default profile.
216  base::string16 non_default_profile_;
217
218  // A user data dir that isn't the default.
219  base::FilePath non_default_user_data_dir_;
220
221  // Chrome's AppUserModelId for the non-default profile.
222  base::string16 non_default_profile_chrome_app_id_;
223
224  // Chrome's AppUserModelId for the non-default user data dir.
225  base::string16 non_default_user_data_dir_chrome_app_id_;
226
227  // Chrome's AppUserModelId for the non-default user data dir and non-default
228  // profile.
229  base::string16 non_default_user_data_dir_and_profile_chrome_app_id_;
230
231  // The app launcher's app id.
232  base::string16 app_list_app_id_;
233
234  // An example extension id of an example app.
235  base::string16 extension_id_;
236
237  // The app id of the example app for the default profile and user data dir.
238  base::string16 extension_app_id_;
239
240  // The app id of the example app for the non-default profile.
241  base::string16 non_default_profile_extension_app_id_;
242};
243
244}  // namespace
245
246// Test migration when not checking for dual mode.
247TEST_F(ShellIntegrationWinMigrateShortcutTest, DontCheckDualMode) {
248  if (base::win::GetVersion() < base::win::VERSION_WIN7)
249    return;
250
251  EXPECT_EQ(9,
252            ShellIntegration::MigrateShortcutsInPathInternal(
253                chrome_exe_, temp_dir_.path(), false));
254
255  // Only shortcut 1, 3, 4, 5, 6, 7, 8, 9, and 10 should have been migrated.
256  shortcuts_[1].properties.set_app_id(chrome_app_id_);
257  shortcuts_[3].properties.set_app_id(chrome_app_id_);
258  shortcuts_[4].properties.set_app_id(chrome_app_id_);
259  shortcuts_[5].properties.set_app_id(chrome_app_id_);
260  shortcuts_[6].properties.set_app_id(non_default_profile_chrome_app_id_);
261  shortcuts_[7].properties.set_app_id(non_default_user_data_dir_chrome_app_id_);
262  shortcuts_[8].properties.set_app_id(
263      non_default_user_data_dir_and_profile_chrome_app_id_);
264  shortcuts_[9].properties.set_app_id(extension_app_id_);
265  shortcuts_[10].properties.set_app_id(non_default_profile_extension_app_id_);
266
267  for (size_t i = 0; i < shortcuts_.size(); ++i) {
268    // Dual mode should be false for all of these.
269    shortcuts_[i].properties.set_dual_mode(false);
270    base::win::ValidateShortcut(shortcuts_[i].path, shortcuts_[i].properties);
271  }
272
273  // Make sure shortcuts are not re-migrated.
274  EXPECT_EQ(0,
275            ShellIntegration::MigrateShortcutsInPathInternal(
276                chrome_exe_, temp_dir_.path(), false));
277}
278
279// Test migration when also checking for dual mode.
280TEST_F(ShellIntegrationWinMigrateShortcutTest, CheckDualMode) {
281  if (base::win::GetVersion() < base::win::VERSION_WIN7)
282    return;
283
284  EXPECT_EQ(10,
285            ShellIntegration::MigrateShortcutsInPathInternal(
286                chrome_exe_, temp_dir_.path(), true));
287
288  // Shortcut 1, 3, 4, 5, 6, 7, 8, 9, and 10 should have had both their app_id
289  // fixed and shortcut 1, 2, 3, 4, and 5 should also have had their dual_mode
290  // property fixed.
291  shortcuts_[1].properties.set_app_id(chrome_app_id_);
292  shortcuts_[3].properties.set_app_id(chrome_app_id_);
293  shortcuts_[4].properties.set_app_id(chrome_app_id_);
294  shortcuts_[5].properties.set_app_id(chrome_app_id_);
295  shortcuts_[6].properties.set_app_id(non_default_profile_chrome_app_id_);
296  shortcuts_[7].properties.set_app_id(non_default_user_data_dir_chrome_app_id_);
297  shortcuts_[8].properties.set_app_id(
298      non_default_user_data_dir_and_profile_chrome_app_id_);
299  shortcuts_[9].properties.set_app_id(extension_app_id_);
300  shortcuts_[10].properties.set_app_id(non_default_profile_extension_app_id_);
301
302  shortcuts_[1].properties.set_dual_mode(true);
303  shortcuts_[2].properties.set_dual_mode(true);
304  shortcuts_[3].properties.set_dual_mode(true);
305  shortcuts_[4].properties.set_dual_mode(true);
306  shortcuts_[5].properties.set_dual_mode(true);
307  shortcuts_[6].properties.set_dual_mode(false);
308  shortcuts_[7].properties.set_dual_mode(false);
309  shortcuts_[8].properties.set_dual_mode(false);
310  shortcuts_[9].properties.set_dual_mode(false);
311  shortcuts_[10].properties.set_dual_mode(false);
312
313  for (size_t i = 0; i < shortcuts_.size(); ++i)
314    base::win::ValidateShortcut(shortcuts_[i].path, shortcuts_[i].properties);
315
316  // Make sure shortcuts are not re-migrated.
317  EXPECT_EQ(0,
318            ShellIntegration::MigrateShortcutsInPathInternal(
319                chrome_exe_, temp_dir_.path(), false));
320}
321
322TEST(ShellIntegrationWinTest, GetAppModelIdForProfileTest) {
323  const base::string16 base_app_id(
324      BrowserDistribution::GetDistribution()->GetBaseAppId());
325
326  // Empty profile path should get chrome::kBrowserAppID
327  base::FilePath empty_path;
328  EXPECT_EQ(base_app_id,
329            ShellIntegration::GetAppModelIdForProfile(base_app_id, empty_path));
330
331  // Default profile path should get chrome::kBrowserAppID
332  base::FilePath default_user_data_dir;
333  chrome::GetDefaultUserDataDirectory(&default_user_data_dir);
334  base::FilePath default_profile_path =
335      default_user_data_dir.AppendASCII(chrome::kInitialProfile);
336  EXPECT_EQ(base_app_id,
337            ShellIntegration::GetAppModelIdForProfile(base_app_id,
338                                                      default_profile_path));
339
340  // Non-default profile path should get chrome::kBrowserAppID joined with
341  // profile info.
342  base::FilePath profile_path(FILE_PATH_LITERAL("root"));
343  profile_path = profile_path.Append(FILE_PATH_LITERAL("udd"));
344  profile_path = profile_path.Append(FILE_PATH_LITERAL("User Data - Test"));
345  EXPECT_EQ(base_app_id + L".udd.UserDataTest",
346            ShellIntegration::GetAppModelIdForProfile(base_app_id,
347                                                      profile_path));
348}
349
350TEST(ShellIntegrationWinTest, GetAppListAppModelIdForProfileTest) {
351  base::string16 base_app_id(
352      BrowserDistribution::GetDistribution()->GetBaseAppId());
353  base_app_id.append(L"AppList");
354
355  // Empty profile path should get chrome::kBrowserAppID + AppList
356  base::FilePath empty_path;
357  EXPECT_EQ(base_app_id,
358            ShellIntegration::GetAppListAppModelIdForProfile(empty_path));
359
360  // Default profile path should get chrome::kBrowserAppID + AppList
361  base::FilePath default_user_data_dir;
362  chrome::GetDefaultUserDataDirectory(&default_user_data_dir);
363  base::FilePath default_profile_path =
364      default_user_data_dir.AppendASCII(chrome::kInitialProfile);
365  EXPECT_EQ(base_app_id,
366            ShellIntegration::GetAppListAppModelIdForProfile(
367                default_profile_path));
368
369  // Non-default profile path should get chrome::kBrowserAppID + AppList joined
370  // with profile info.
371  base::FilePath profile_path(FILE_PATH_LITERAL("root"));
372  profile_path = profile_path.Append(FILE_PATH_LITERAL("udd"));
373  profile_path = profile_path.Append(FILE_PATH_LITERAL("User Data - Test"));
374  EXPECT_EQ(base_app_id + L".udd.UserDataTest",
375            ShellIntegration::GetAppListAppModelIdForProfile(profile_path));
376}
377