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.
5 #include "chrome/browser/shell_integration.h"
10 #include "base/base_paths.h"
11 #include "base/file_util.h"
12 #include "base/files/file_path.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/message_loop.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/test/scoped_path_override.h"
19 #include "chrome/browser/web_applications/web_app.h"
20 #include "chrome/common/chrome_constants.h"
21 #include "content/public/test/test_browser_thread.h"
22 #include "googleurl/src/gurl.h"
23 #include "testing/gtest/include/gtest/gtest.h"
25 #if defined(OS_POSIX) && !defined(OS_MACOSX)
26 #include "base/environment.h"
27 #include "chrome/browser/shell_integration_linux.h"
30 #define FPL FILE_PATH_LITERAL
32 using content::BrowserThread
;
34 #if defined(OS_POSIX) && !defined(OS_MACOSX)
37 // Provides mock environment variables values based on a stored map.
38 class MockEnvironment
: public base::Environment
{
42 void Set(const std::string
& name
, const std::string
& value
) {
43 variables_
[name
] = value
;
46 virtual bool GetVar(const char* variable_name
, std::string
* result
) OVERRIDE
{
47 if (ContainsKey(variables_
, variable_name
)) {
48 *result
= variables_
[variable_name
];
55 virtual bool SetVar(const char* variable_name
,
56 const std::string
& new_value
) OVERRIDE
{
61 virtual bool UnSetVar(const char* variable_name
) OVERRIDE
{
67 std::map
<std::string
, std::string
> variables_
;
69 DISALLOW_COPY_AND_ASSIGN(MockEnvironment
);
74 TEST(ShellIntegrationTest
, GetExistingShortcutLocations
) {
75 base::FilePath
kProfilePath("Default");
76 const char kExtensionId
[] = "test_extension";
77 const char kTemplateFilename
[] = "chrome-test_extension-Default.desktop";
78 base::FilePath
kTemplateFilepath(kTemplateFilename
);
79 const char kNoDisplayDesktopFile
[] = "[Desktop Entry]\nNoDisplay=true";
81 base::MessageLoop message_loop
;
82 content::TestBrowserThread
file_thread(BrowserThread::FILE, &message_loop
);
84 // No existing shortcuts.
87 ShellIntegration::ShortcutLocations result
=
88 ShellIntegrationLinux::GetExistingShortcutLocations(
89 &env
, kProfilePath
, kExtensionId
);
90 EXPECT_FALSE(result
.on_desktop
);
91 EXPECT_FALSE(result
.in_applications_menu
);
92 EXPECT_FALSE(result
.in_quick_launch_bar
);
93 EXPECT_FALSE(result
.hidden
);
96 // Shortcut on desktop.
98 base::ScopedTempDir temp_dir
;
99 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
100 base::FilePath desktop_path
= temp_dir
.path();
103 ASSERT_TRUE(file_util::CreateDirectory(desktop_path
));
104 ASSERT_FALSE(file_util::WriteFile(
105 desktop_path
.AppendASCII(kTemplateFilename
),
107 ShellIntegration::ShortcutLocations result
=
108 ShellIntegrationLinux::GetExistingShortcutLocations(
109 &env
, kProfilePath
, kExtensionId
, desktop_path
);
110 EXPECT_TRUE(result
.on_desktop
);
111 EXPECT_FALSE(result
.in_applications_menu
);
112 EXPECT_FALSE(result
.in_quick_launch_bar
);
113 EXPECT_FALSE(result
.hidden
);
116 // Shortcut in applications directory.
118 base::ScopedTempDir temp_dir
;
119 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
120 base::FilePath apps_path
= temp_dir
.path().AppendASCII("applications");
123 env
.Set("XDG_DATA_HOME", temp_dir
.path().value());
124 ASSERT_TRUE(file_util::CreateDirectory(apps_path
));
125 ASSERT_FALSE(file_util::WriteFile(
126 apps_path
.AppendASCII(kTemplateFilename
),
128 ShellIntegration::ShortcutLocations result
=
129 ShellIntegrationLinux::GetExistingShortcutLocations(
130 &env
, kProfilePath
, kExtensionId
);
131 EXPECT_FALSE(result
.on_desktop
);
132 EXPECT_TRUE(result
.in_applications_menu
);
133 EXPECT_FALSE(result
.in_quick_launch_bar
);
134 EXPECT_FALSE(result
.hidden
);
137 // Shortcut in applications directory with NoDisplay=true.
139 base::ScopedTempDir temp_dir
;
140 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
141 base::FilePath apps_path
= temp_dir
.path().AppendASCII("applications");
144 env
.Set("XDG_DATA_HOME", temp_dir
.path().value());
145 ASSERT_TRUE(file_util::CreateDirectory(apps_path
));
146 ASSERT_TRUE(file_util::WriteFile(
147 apps_path
.AppendASCII(kTemplateFilename
),
148 kNoDisplayDesktopFile
, strlen(kNoDisplayDesktopFile
)));
149 ShellIntegration::ShortcutLocations result
=
150 ShellIntegrationLinux::GetExistingShortcutLocations(
151 &env
, kProfilePath
, kExtensionId
);
152 // Doesn't count as being in applications menu.
153 EXPECT_FALSE(result
.on_desktop
);
154 EXPECT_FALSE(result
.in_applications_menu
);
155 EXPECT_FALSE(result
.in_quick_launch_bar
);
156 EXPECT_TRUE(result
.hidden
);
159 // Shortcut on desktop and in applications directory.
161 base::ScopedTempDir temp_dir1
;
162 ASSERT_TRUE(temp_dir1
.CreateUniqueTempDir());
163 base::FilePath desktop_path
= temp_dir1
.path();
165 base::ScopedTempDir temp_dir2
;
166 ASSERT_TRUE(temp_dir2
.CreateUniqueTempDir());
167 base::FilePath apps_path
= temp_dir2
.path().AppendASCII("applications");
170 ASSERT_TRUE(file_util::CreateDirectory(desktop_path
));
171 ASSERT_FALSE(file_util::WriteFile(
172 desktop_path
.AppendASCII(kTemplateFilename
),
174 env
.Set("XDG_DATA_HOME", temp_dir2
.path().value());
175 ASSERT_TRUE(file_util::CreateDirectory(apps_path
));
176 ASSERT_FALSE(file_util::WriteFile(
177 apps_path
.AppendASCII(kTemplateFilename
),
179 ShellIntegration::ShortcutLocations result
=
180 ShellIntegrationLinux::GetExistingShortcutLocations(
181 &env
, kProfilePath
, kExtensionId
, desktop_path
);
182 EXPECT_TRUE(result
.on_desktop
);
183 EXPECT_TRUE(result
.in_applications_menu
);
184 EXPECT_FALSE(result
.in_quick_launch_bar
);
185 EXPECT_FALSE(result
.hidden
);
189 TEST(ShellIntegrationTest
, GetExistingShortcutContents
) {
190 const char kTemplateFilename
[] = "shortcut-test.desktop";
191 base::FilePath
kTemplateFilepath(kTemplateFilename
);
192 const char kTestData1
[] = "a magical testing string";
193 const char kTestData2
[] = "a different testing string";
195 base::MessageLoop message_loop
;
196 content::TestBrowserThread
file_thread(BrowserThread::FILE, &message_loop
);
198 // Test that it searches $XDG_DATA_HOME/applications.
200 base::ScopedTempDir temp_dir
;
201 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
204 env
.Set("XDG_DATA_HOME", temp_dir
.path().value());
205 // Create a file in a non-applications directory. This should be ignored.
206 ASSERT_TRUE(file_util::WriteFile(
207 temp_dir
.path().AppendASCII(kTemplateFilename
),
208 kTestData2
, strlen(kTestData2
)));
209 ASSERT_TRUE(file_util::CreateDirectory(
210 temp_dir
.path().AppendASCII("applications")));
211 ASSERT_TRUE(file_util::WriteFile(
212 temp_dir
.path().AppendASCII("applications")
213 .AppendASCII(kTemplateFilename
),
214 kTestData1
, strlen(kTestData1
)));
215 std::string contents
;
217 ShellIntegrationLinux::GetExistingShortcutContents(
218 &env
, kTemplateFilepath
, &contents
));
219 EXPECT_EQ(kTestData1
, contents
);
222 // Test that it falls back to $HOME/.local/share/applications.
224 base::ScopedTempDir temp_dir
;
225 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
228 env
.Set("HOME", temp_dir
.path().value());
229 ASSERT_TRUE(file_util::CreateDirectory(
230 temp_dir
.path().AppendASCII(".local/share/applications")));
231 ASSERT_TRUE(file_util::WriteFile(
232 temp_dir
.path().AppendASCII(".local/share/applications")
233 .AppendASCII(kTemplateFilename
),
234 kTestData1
, strlen(kTestData1
)));
235 std::string contents
;
237 ShellIntegrationLinux::GetExistingShortcutContents(
238 &env
, kTemplateFilepath
, &contents
));
239 EXPECT_EQ(kTestData1
, contents
);
242 // Test that it searches $XDG_DATA_DIRS/applications.
244 base::ScopedTempDir temp_dir
;
245 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
248 env
.Set("XDG_DATA_DIRS", temp_dir
.path().value());
249 ASSERT_TRUE(file_util::CreateDirectory(
250 temp_dir
.path().AppendASCII("applications")));
251 ASSERT_TRUE(file_util::WriteFile(
252 temp_dir
.path().AppendASCII("applications")
253 .AppendASCII(kTemplateFilename
),
254 kTestData2
, strlen(kTestData2
)));
255 std::string contents
;
257 ShellIntegrationLinux::GetExistingShortcutContents(
258 &env
, kTemplateFilepath
, &contents
));
259 EXPECT_EQ(kTestData2
, contents
);
262 // Test that it searches $X/applications for each X in $XDG_DATA_DIRS.
264 base::ScopedTempDir temp_dir1
;
265 ASSERT_TRUE(temp_dir1
.CreateUniqueTempDir());
266 base::ScopedTempDir temp_dir2
;
267 ASSERT_TRUE(temp_dir2
.CreateUniqueTempDir());
270 env
.Set("XDG_DATA_DIRS", temp_dir1
.path().value() + ":" +
271 temp_dir2
.path().value());
272 // Create a file in a non-applications directory. This should be ignored.
273 ASSERT_TRUE(file_util::WriteFile(
274 temp_dir1
.path().AppendASCII(kTemplateFilename
),
275 kTestData1
, strlen(kTestData1
)));
276 // Only create a findable desktop file in the second path.
277 ASSERT_TRUE(file_util::CreateDirectory(
278 temp_dir2
.path().AppendASCII("applications")));
279 ASSERT_TRUE(file_util::WriteFile(
280 temp_dir2
.path().AppendASCII("applications")
281 .AppendASCII(kTemplateFilename
),
282 kTestData2
, strlen(kTestData2
)));
283 std::string contents
;
285 ShellIntegrationLinux::GetExistingShortcutContents(
286 &env
, kTemplateFilepath
, &contents
));
287 EXPECT_EQ(kTestData2
, contents
);
291 TEST(ShellIntegrationTest
, GetWebShortcutFilename
) {
293 const base::FilePath::CharType
* path
;
296 { FPL("http___foo_.desktop"), "http://foo" },
297 { FPL("http___foo_bar_.desktop"), "http://foo/bar/" },
298 { FPL("http___foo_bar_a=b&c=d.desktop"), "http://foo/bar?a=b&c=d" },
300 // Now we're starting to be more evil...
301 { FPL("http___foo_.desktop"), "http://foo/bar/baz/../../../../../" },
302 { FPL("http___foo_.desktop"), "http://foo/bar/././../baz/././../" },
303 { FPL("http___.._.desktop"), "http://../../../../" },
305 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(test_cases
); i
++) {
306 EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName
) + "-" +
308 ShellIntegrationLinux::GetWebShortcutFilename(
309 GURL(test_cases
[i
].url
)).value()) <<
310 " while testing " << test_cases
[i
].url
;
314 TEST(ShellIntegrationTest
, GetDesktopFileContents
) {
315 const base::FilePath
kChromeExePath("/opt/google/chrome/google-chrome");
319 const char* icon_name
;
321 const char* expected_output
;
324 { "http://gmail.com",
326 "chrome-http__gmail.com",
329 "#!/usr/bin/env xdg-open\n"
335 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
336 "Icon=chrome-http__gmail.com\n"
337 #if !defined(USE_AURA)
338 // Aura Chrome does not (yet) set WMClass, so we only expect
339 // StartupWMClass on non-Aura builds.
340 "StartupWMClass=gmail.com\n"
344 // Make sure that empty icons are replaced by the chrome icon.
345 { "http://gmail.com",
350 "#!/usr/bin/env xdg-open\n"
356 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
357 "Icon=chromium-browser\n"
358 #if !defined(USE_AURA)
359 // Aura Chrome does not (yet) set WMClass, so we only expect
360 // StartupWMClass on non-Aura builds.
361 "StartupWMClass=gmail.com\n"
365 // Test adding NoDisplay=true.
366 { "http://gmail.com",
368 "chrome-http__gmail.com",
371 "#!/usr/bin/env xdg-open\n"
377 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
378 "Icon=chrome-http__gmail.com\n"
380 #if !defined(USE_AURA)
381 // Aura Chrome does not (yet) set WMClass, so we only expect
382 // StartupWMClass on non-Aura builds.
383 "StartupWMClass=gmail.com\n"
387 // Now we're starting to be more evil...
388 { "http://evil.com/evil --join-the-b0tnet",
389 "Ownz0red\nExec=rm -rf /",
390 "chrome-http__evil.com_evil",
393 "#!/usr/bin/env xdg-open\n"
398 "Name=http://evil.com/evil%20--join-the-b0tnet\n"
399 "Exec=/opt/google/chrome/google-chrome "
400 "--app=http://evil.com/evil%20--join-the-b0tnet\n"
401 "Icon=chrome-http__evil.com_evil\n"
402 #if !defined(USE_AURA)
403 // Aura Chrome does not (yet) set WMClass, so we only expect
404 // StartupWMClass on non-Aura builds.
405 "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"
408 { "http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
410 "chrome-http__evil.com_evil",
413 "#!/usr/bin/env xdg-open\n"
418 "Name=Innocent Title\n"
419 "Exec=/opt/google/chrome/google-chrome "
420 "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20"
421 // Note: $ is escaped as \$ within an arg to Exec, and then
422 // the \ is escaped as \\ as all strings in a Desktop file should
423 // be; finally, \\ becomes \\\\ when represented in a C++ string!
424 "-rf%20\\\\$HOME%20%3Eownz0red\"\n"
425 "Icon=chrome-http__evil.com_evil\n"
426 #if !defined(USE_AURA)
427 // Aura Chrome does not (yet) set WMClass, so we only expect
428 // StartupWMClass on non-Aura builds.
429 "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
430 "rm%20-rf%20$HOME%20%3Eownz0red\n"
433 { "http://evil.com/evil | cat `echo ownz0red` >/dev/null",
435 "chrome-http__evil.com_evil",
438 "#!/usr/bin/env xdg-open\n"
443 "Name=Innocent Title\n"
444 "Exec=/opt/google/chrome/google-chrome "
445 "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red"
446 "%60%20%3E/dev/null\n"
447 "Icon=chrome-http__evil.com_evil\n"
448 #if !defined(USE_AURA)
449 // Aura Chrome does not (yet) set WMClass, so we only expect
450 // StartupWMClass on non-Aura builds.
451 "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
452 "%60%20%3E_dev_null\n"
457 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(test_cases
); i
++) {
460 test_cases
[i
].expected_output
,
461 ShellIntegrationLinux::GetDesktopFileContents(
463 web_app::GenerateApplicationNameFromURL(GURL(test_cases
[i
].url
)),
464 GURL(test_cases
[i
].url
),
467 ASCIIToUTF16(test_cases
[i
].title
),
468 test_cases
[i
].icon_name
,
470 test_cases
[i
].nodisplay
));
474 TEST(ShellIntegrationTest
, GetDirectoryFileContents
) {
477 const char* icon_name
;
478 const char* expected_output
;
491 // Make sure that empty icons are replaced by the chrome icon.
499 "Icon=chromium-browser\n"
503 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(test_cases
); i
++) {
506 test_cases
[i
].expected_output
,
507 ShellIntegrationLinux::GetDirectoryFileContents(
508 ASCIIToUTF16(test_cases
[i
].title
),
509 test_cases
[i
].icon_name
));