Don't crash when SimpleCache index is corrupt.
[chromium-blink-merge.git] / chrome / browser / shell_integration_unittest.cc
blob141daee7619ea0730db04a79c6f195c13313cb39
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"
7 #include <cstdlib>
8 #include <map>
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"
28 #endif
30 #define FPL FILE_PATH_LITERAL
32 using content::BrowserThread;
34 #if defined(OS_POSIX) && !defined(OS_MACOSX)
35 namespace {
37 // Provides mock environment variables values based on a stored map.
38 class MockEnvironment : public base::Environment {
39 public:
40 MockEnvironment() {}
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];
49 return true;
52 return false;
55 virtual bool SetVar(const char* variable_name,
56 const std::string& new_value) OVERRIDE {
57 ADD_FAILURE();
58 return false;
61 virtual bool UnSetVar(const char* variable_name) OVERRIDE {
62 ADD_FAILURE();
63 return false;
66 private:
67 std::map<std::string, std::string> variables_;
69 DISALLOW_COPY_AND_ASSIGN(MockEnvironment);
72 } // namespace
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.
86 MockEnvironment env;
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();
102 MockEnvironment env;
103 ASSERT_TRUE(file_util::CreateDirectory(desktop_path));
104 ASSERT_FALSE(file_util::WriteFile(
105 desktop_path.AppendASCII(kTemplateFilename),
106 "", 0));
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");
122 MockEnvironment env;
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),
127 "", 0));
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");
143 MockEnvironment env;
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");
169 MockEnvironment env;
170 ASSERT_TRUE(file_util::CreateDirectory(desktop_path));
171 ASSERT_FALSE(file_util::WriteFile(
172 desktop_path.AppendASCII(kTemplateFilename),
173 "", 0));
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),
178 "", 0));
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());
203 MockEnvironment env;
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;
216 ASSERT_TRUE(
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());
227 MockEnvironment env;
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;
236 ASSERT_TRUE(
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());
247 MockEnvironment env;
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;
256 ASSERT_TRUE(
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());
269 MockEnvironment env;
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;
284 ASSERT_TRUE(
285 ShellIntegrationLinux::GetExistingShortcutContents(
286 &env, kTemplateFilepath, &contents));
287 EXPECT_EQ(kTestData2, contents);
291 TEST(ShellIntegrationTest, GetWebShortcutFilename) {
292 const struct {
293 const base::FilePath::CharType* path;
294 const char* url;
295 } test_cases[] = {
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) + "-" +
307 test_cases[i].path,
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");
316 const struct {
317 const char* url;
318 const char* title;
319 const char* icon_name;
320 bool nodisplay;
321 const char* expected_output;
322 } test_cases[] = {
323 // Real-world case.
324 { "http://gmail.com",
325 "GMail",
326 "chrome-http__gmail.com",
327 false,
329 "#!/usr/bin/env xdg-open\n"
330 "[Desktop Entry]\n"
331 "Version=1.0\n"
332 "Terminal=false\n"
333 "Type=Application\n"
334 "Name=GMail\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"
341 #endif
344 // Make sure that empty icons are replaced by the chrome icon.
345 { "http://gmail.com",
346 "GMail",
348 false,
350 "#!/usr/bin/env xdg-open\n"
351 "[Desktop Entry]\n"
352 "Version=1.0\n"
353 "Terminal=false\n"
354 "Type=Application\n"
355 "Name=GMail\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"
362 #endif
365 // Test adding NoDisplay=true.
366 { "http://gmail.com",
367 "GMail",
368 "chrome-http__gmail.com",
369 true,
371 "#!/usr/bin/env xdg-open\n"
372 "[Desktop Entry]\n"
373 "Version=1.0\n"
374 "Terminal=false\n"
375 "Type=Application\n"
376 "Name=GMail\n"
377 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
378 "Icon=chrome-http__gmail.com\n"
379 "NoDisplay=true\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"
384 #endif
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",
391 false,
393 "#!/usr/bin/env xdg-open\n"
394 "[Desktop Entry]\n"
395 "Version=1.0\n"
396 "Terminal=false\n"
397 "Type=Application\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"
406 #endif
408 { "http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
409 "Innocent Title",
410 "chrome-http__evil.com_evil",
411 false,
413 "#!/usr/bin/env xdg-open\n"
414 "[Desktop Entry]\n"
415 "Version=1.0\n"
416 "Terminal=false\n"
417 "Type=Application\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"
431 #endif
433 { "http://evil.com/evil | cat `echo ownz0red` >/dev/null",
434 "Innocent Title",
435 "chrome-http__evil.com_evil",
436 false,
438 "#!/usr/bin/env xdg-open\n"
439 "[Desktop Entry]\n"
440 "Version=1.0\n"
441 "Terminal=false\n"
442 "Type=Application\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"
453 #endif
457 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
458 SCOPED_TRACE(i);
459 EXPECT_EQ(
460 test_cases[i].expected_output,
461 ShellIntegrationLinux::GetDesktopFileContents(
462 kChromeExePath,
463 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
464 GURL(test_cases[i].url),
465 std::string(),
466 base::FilePath(),
467 ASCIIToUTF16(test_cases[i].title),
468 test_cases[i].icon_name,
469 base::FilePath(),
470 test_cases[i].nodisplay));
474 TEST(ShellIntegrationTest, GetDirectoryFileContents) {
475 const struct {
476 const char* title;
477 const char* icon_name;
478 const char* expected_output;
479 } test_cases[] = {
480 // Real-world case.
481 { "Chrome Apps",
482 "chrome-apps",
484 "[Desktop Entry]\n"
485 "Version=1.0\n"
486 "Type=Directory\n"
487 "Name=Chrome Apps\n"
488 "Icon=chrome-apps\n"
491 // Make sure that empty icons are replaced by the chrome icon.
492 { "Chrome Apps",
495 "[Desktop Entry]\n"
496 "Version=1.0\n"
497 "Type=Directory\n"
498 "Name=Chrome Apps\n"
499 "Icon=chromium-browser\n"
503 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
504 SCOPED_TRACE(i);
505 EXPECT_EQ(
506 test_cases[i].expected_output,
507 ShellIntegrationLinux::GetDirectoryFileContents(
508 ASCIIToUTF16(test_cases[i].title),
509 test_cases[i].icon_name));
513 #endif