1// Copyright 2013 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_linux.h"
6
7#include <algorithm>
8#include <cstdlib>
9#include <map>
10
11#include "base/base_paths.h"
12#include "base/command_line.h"
13#include "base/environment.h"
14#include "base/files/file_path.h"
15#include "base/files/file_util.h"
16#include "base/files/scoped_temp_dir.h"
17#include "base/message_loop/message_loop.h"
18#include "base/stl_util.h"
19#include "base/strings/string_util.h"
20#include "base/strings/utf_string_conversions.h"
21#include "base/test/scoped_path_override.h"
22#include "chrome/common/chrome_constants.h"
23#include "content/public/test/test_browser_thread.h"
24#include "testing/gmock/include/gmock/gmock.h"
25#include "testing/gtest/include/gtest/gtest.h"
26#include "url/gurl.h"
27
28#define FPL FILE_PATH_LITERAL
29
30using content::BrowserThread;
31using ::testing::ElementsAre;
32
33namespace shell_integration_linux {
34
35namespace {
36
37// Provides mock environment variables values based on a stored map.
38class MockEnvironment : public base::Environment {
39 public:
40  MockEnvironment() {}
41
42  void Set(const std::string& name, const std::string& value) {
43    variables_[name] = value;
44  }
45
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;
50    }
51
52    return false;
53  }
54
55  virtual bool SetVar(const char* variable_name,
56                      const std::string& new_value) OVERRIDE {
57    ADD_FAILURE();
58    return false;
59  }
60
61  virtual bool UnSetVar(const char* variable_name) OVERRIDE {
62    ADD_FAILURE();
63    return false;
64  }
65
66 private:
67  std::map<std::string, std::string> variables_;
68
69  DISALLOW_COPY_AND_ASSIGN(MockEnvironment);
70};
71
72}  // namespace
73
74TEST(ShellIntegrationTest, GetDataWriteLocation) {
75  base::MessageLoop message_loop;
76  content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop);
77
78  // Test that it returns $XDG_DATA_HOME.
79  {
80    MockEnvironment env;
81    env.Set("HOME", "/home/user");
82    env.Set("XDG_DATA_HOME", "/user/path");
83    base::FilePath path;
84    ASSERT_TRUE(GetDataWriteLocation(&env, &path));
85    EXPECT_EQ(base::FilePath("/user/path"), path);
86  }
87
88  // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
89  {
90    MockEnvironment env;
91    env.Set("HOME", "/home/user");
92    base::FilePath path;
93    ASSERT_TRUE(GetDataWriteLocation(&env, &path));
94    EXPECT_EQ(base::FilePath("/home/user/.local/share"), path);
95  }
96
97  // Test that if neither $XDG_DATA_HOME nor $HOME are specified, it fails.
98  {
99    MockEnvironment env;
100    base::FilePath path;
101    ASSERT_FALSE(GetDataWriteLocation(&env, &path));
102  }
103}
104
105TEST(ShellIntegrationTest, GetDataSearchLocations) {
106  base::MessageLoop message_loop;
107  content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop);
108
109  // Test that it returns $XDG_DATA_HOME + $XDG_DATA_DIRS.
110  {
111    MockEnvironment env;
112    env.Set("HOME", "/home/user");
113    env.Set("XDG_DATA_HOME", "/user/path");
114    env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
115    EXPECT_THAT(
116        GetDataSearchLocations(&env),
117        ElementsAre(base::FilePath("/user/path"),
118                    base::FilePath("/system/path/1"),
119                    base::FilePath("/system/path/2")));
120  }
121
122  // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
123  {
124    MockEnvironment env;
125    env.Set("HOME", "/home/user");
126    env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
127    EXPECT_THAT(
128        GetDataSearchLocations(&env),
129        ElementsAre(base::FilePath("/home/user/.local/share"),
130                    base::FilePath("/system/path/1"),
131                    base::FilePath("/system/path/2")));
132  }
133
134  // Test that if neither $XDG_DATA_HOME nor $HOME are specified, it still
135  // succeeds.
136  {
137    MockEnvironment env;
138    env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
139    EXPECT_THAT(
140        GetDataSearchLocations(&env),
141        ElementsAre(base::FilePath("/system/path/1"),
142                    base::FilePath("/system/path/2")));
143  }
144
145  // Test that $XDG_DATA_DIRS falls back to the two default paths.
146  {
147    MockEnvironment env;
148    env.Set("HOME", "/home/user");
149    env.Set("XDG_DATA_HOME", "/user/path");
150    EXPECT_THAT(
151        GetDataSearchLocations(&env),
152        ElementsAre(base::FilePath("/user/path"),
153                    base::FilePath("/usr/local/share"),
154                    base::FilePath("/usr/share")));
155  }
156}
157
158TEST(ShellIntegrationTest, GetExistingShortcutLocations) {
159  base::FilePath kProfilePath("Profile 1");
160  const char kExtensionId[] = "test_extension";
161  const char kTemplateFilename[] = "chrome-test_extension-Profile_1.desktop";
162  base::FilePath kTemplateFilepath(kTemplateFilename);
163  const char kNoDisplayDesktopFile[] = "[Desktop Entry]\nNoDisplay=true";
164
165  base::MessageLoop message_loop;
166  content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop);
167
168  // No existing shortcuts.
169  {
170    MockEnvironment env;
171    web_app::ShortcutLocations result =
172        GetExistingShortcutLocations(&env, kProfilePath, kExtensionId);
173    EXPECT_FALSE(result.on_desktop);
174    EXPECT_EQ(web_app::APP_MENU_LOCATION_NONE,
175              result.applications_menu_location);
176
177    EXPECT_FALSE(result.in_quick_launch_bar);
178  }
179
180  // Shortcut on desktop.
181  {
182    base::ScopedTempDir temp_dir;
183    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
184    base::FilePath desktop_path = temp_dir.path();
185
186    MockEnvironment env;
187    ASSERT_TRUE(base::CreateDirectory(desktop_path));
188    ASSERT_FALSE(base::WriteFile(
189        desktop_path.AppendASCII(kTemplateFilename),
190        "", 0));
191    web_app::ShortcutLocations result = GetExistingShortcutLocations(
192        &env, kProfilePath, kExtensionId, desktop_path);
193    EXPECT_TRUE(result.on_desktop);
194    EXPECT_EQ(web_app::APP_MENU_LOCATION_NONE,
195              result.applications_menu_location);
196
197    EXPECT_FALSE(result.in_quick_launch_bar);
198  }
199
200  // Shortcut in applications directory.
201  {
202    base::ScopedTempDir temp_dir;
203    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
204    base::FilePath apps_path = temp_dir.path().AppendASCII("applications");
205
206    MockEnvironment env;
207    env.Set("XDG_DATA_HOME", temp_dir.path().value());
208    ASSERT_TRUE(base::CreateDirectory(apps_path));
209    ASSERT_FALSE(base::WriteFile(
210        apps_path.AppendASCII(kTemplateFilename),
211        "", 0));
212    web_app::ShortcutLocations result =
213        GetExistingShortcutLocations(&env, kProfilePath, kExtensionId);
214    EXPECT_FALSE(result.on_desktop);
215    EXPECT_EQ(web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS,
216              result.applications_menu_location);
217
218    EXPECT_FALSE(result.in_quick_launch_bar);
219  }
220
221  // Shortcut in applications directory with NoDisplay=true.
222  {
223    base::ScopedTempDir temp_dir;
224    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
225    base::FilePath apps_path = temp_dir.path().AppendASCII("applications");
226
227    MockEnvironment env;
228    env.Set("XDG_DATA_HOME", temp_dir.path().value());
229    ASSERT_TRUE(base::CreateDirectory(apps_path));
230    ASSERT_TRUE(base::WriteFile(
231        apps_path.AppendASCII(kTemplateFilename),
232        kNoDisplayDesktopFile, strlen(kNoDisplayDesktopFile)));
233    web_app::ShortcutLocations result =
234        GetExistingShortcutLocations(&env, kProfilePath, kExtensionId);
235    // Doesn't count as being in applications menu.
236    EXPECT_FALSE(result.on_desktop);
237    EXPECT_EQ(web_app::APP_MENU_LOCATION_HIDDEN,
238              result.applications_menu_location);
239    EXPECT_FALSE(result.in_quick_launch_bar);
240  }
241
242  // Shortcut on desktop and in applications directory.
243  {
244    base::ScopedTempDir temp_dir1;
245    ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
246    base::FilePath desktop_path = temp_dir1.path();
247
248    base::ScopedTempDir temp_dir2;
249    ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
250    base::FilePath apps_path = temp_dir2.path().AppendASCII("applications");
251
252    MockEnvironment env;
253    ASSERT_TRUE(base::CreateDirectory(desktop_path));
254    ASSERT_FALSE(base::WriteFile(
255        desktop_path.AppendASCII(kTemplateFilename),
256        "", 0));
257    env.Set("XDG_DATA_HOME", temp_dir2.path().value());
258    ASSERT_TRUE(base::CreateDirectory(apps_path));
259    ASSERT_FALSE(base::WriteFile(
260        apps_path.AppendASCII(kTemplateFilename),
261        "", 0));
262    web_app::ShortcutLocations result = GetExistingShortcutLocations(
263        &env, kProfilePath, kExtensionId, desktop_path);
264    EXPECT_TRUE(result.on_desktop);
265    EXPECT_EQ(web_app::APP_MENU_LOCATION_SUBDIR_CHROMEAPPS,
266              result.applications_menu_location);
267    EXPECT_FALSE(result.in_quick_launch_bar);
268  }
269}
270
271TEST(ShellIntegrationTest, GetExistingShortcutContents) {
272  const char kTemplateFilename[] = "shortcut-test.desktop";
273  base::FilePath kTemplateFilepath(kTemplateFilename);
274  const char kTestData1[] = "a magical testing string";
275  const char kTestData2[] = "a different testing string";
276
277  base::MessageLoop message_loop;
278  content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop);
279
280  // Test that it searches $XDG_DATA_HOME/applications.
281  {
282    base::ScopedTempDir temp_dir;
283    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
284
285    MockEnvironment env;
286    env.Set("XDG_DATA_HOME", temp_dir.path().value());
287    // Create a file in a non-applications directory. This should be ignored.
288    ASSERT_TRUE(base::WriteFile(
289        temp_dir.path().AppendASCII(kTemplateFilename),
290        kTestData2, strlen(kTestData2)));
291    ASSERT_TRUE(base::CreateDirectory(
292        temp_dir.path().AppendASCII("applications")));
293    ASSERT_TRUE(base::WriteFile(
294        temp_dir.path().AppendASCII("applications")
295            .AppendASCII(kTemplateFilename),
296        kTestData1, strlen(kTestData1)));
297    std::string contents;
298    ASSERT_TRUE(
299        GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
300    EXPECT_EQ(kTestData1, contents);
301  }
302
303  // Test that it falls back to $HOME/.local/share/applications.
304  {
305    base::ScopedTempDir temp_dir;
306    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
307
308    MockEnvironment env;
309    env.Set("HOME", temp_dir.path().value());
310    ASSERT_TRUE(base::CreateDirectory(
311        temp_dir.path().AppendASCII(".local/share/applications")));
312    ASSERT_TRUE(base::WriteFile(
313        temp_dir.path().AppendASCII(".local/share/applications")
314            .AppendASCII(kTemplateFilename),
315        kTestData1, strlen(kTestData1)));
316    std::string contents;
317    ASSERT_TRUE(
318        GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
319    EXPECT_EQ(kTestData1, contents);
320  }
321
322  // Test that it searches $XDG_DATA_DIRS/applications.
323  {
324    base::ScopedTempDir temp_dir;
325    ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
326
327    MockEnvironment env;
328    env.Set("XDG_DATA_DIRS", temp_dir.path().value());
329    ASSERT_TRUE(base::CreateDirectory(
330        temp_dir.path().AppendASCII("applications")));
331    ASSERT_TRUE(base::WriteFile(
332        temp_dir.path().AppendASCII("applications")
333            .AppendASCII(kTemplateFilename),
334        kTestData2, strlen(kTestData2)));
335    std::string contents;
336    ASSERT_TRUE(
337        GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
338    EXPECT_EQ(kTestData2, contents);
339  }
340
341  // Test that it searches $X/applications for each X in $XDG_DATA_DIRS.
342  {
343    base::ScopedTempDir temp_dir1;
344    ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
345    base::ScopedTempDir temp_dir2;
346    ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
347
348    MockEnvironment env;
349    env.Set("XDG_DATA_DIRS", temp_dir1.path().value() + ":" +
350                             temp_dir2.path().value());
351    // Create a file in a non-applications directory. This should be ignored.
352    ASSERT_TRUE(base::WriteFile(
353        temp_dir1.path().AppendASCII(kTemplateFilename),
354        kTestData1, strlen(kTestData1)));
355    // Only create a findable desktop file in the second path.
356    ASSERT_TRUE(base::CreateDirectory(
357        temp_dir2.path().AppendASCII("applications")));
358    ASSERT_TRUE(base::WriteFile(
359        temp_dir2.path().AppendASCII("applications")
360            .AppendASCII(kTemplateFilename),
361        kTestData2, strlen(kTestData2)));
362    std::string contents;
363    ASSERT_TRUE(
364        GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
365    EXPECT_EQ(kTestData2, contents);
366  }
367}
368
369TEST(ShellIntegrationTest, GetExtensionShortcutFilename) {
370  base::FilePath kProfilePath("a/b/c/Profile Name?");
371  const char kExtensionId[] = "extensionid";
372  EXPECT_EQ(base::FilePath("chrome-extensionid-Profile_Name_.desktop"),
373            GetExtensionShortcutFilename(kProfilePath, kExtensionId));
374}
375
376TEST(ShellIntegrationTest, GetExistingProfileShortcutFilenames) {
377  base::FilePath kProfilePath("a/b/c/Profile Name?");
378  const char kApp1Filename[] = "chrome-extension1-Profile_Name_.desktop";
379  const char kApp2Filename[] = "chrome-extension2-Profile_Name_.desktop";
380  const char kUnrelatedAppFilename[] = "chrome-extension-Other_Profile.desktop";
381
382  base::MessageLoop message_loop;
383  content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop);
384
385  base::ScopedTempDir temp_dir;
386  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
387  ASSERT_EQ(0,
388            base::WriteFile(
389                temp_dir.path().AppendASCII(kApp1Filename), "", 0));
390  ASSERT_EQ(0,
391            base::WriteFile(
392                temp_dir.path().AppendASCII(kApp2Filename), "", 0));
393  // This file should not be returned in the results.
394  ASSERT_EQ(0,
395            base::WriteFile(
396                temp_dir.path().AppendASCII(kUnrelatedAppFilename), "", 0));
397  std::vector<base::FilePath> paths =
398      GetExistingProfileShortcutFilenames(kProfilePath, temp_dir.path());
399  // Path order is arbitrary. Sort the output for consistency.
400  std::sort(paths.begin(), paths.end());
401  EXPECT_THAT(paths,
402              ElementsAre(base::FilePath(kApp1Filename),
403                          base::FilePath(kApp2Filename)));
404}
405
406TEST(ShellIntegrationTest, GetWebShortcutFilename) {
407  const struct {
408    const base::FilePath::CharType* path;
409    const char* url;
410  } test_cases[] = {
411    { FPL("http___foo_.desktop"), "http://foo" },
412    { FPL("http___foo_bar_.desktop"), "http://foo/bar/" },
413    { FPL("http___foo_bar_a=b&c=d.desktop"), "http://foo/bar?a=b&c=d" },
414
415    // Now we're starting to be more evil...
416    { FPL("http___foo_.desktop"), "http://foo/bar/baz/../../../../../" },
417    { FPL("http___foo_.desktop"), "http://foo/bar/././../baz/././../" },
418    { FPL("http___.._.desktop"), "http://../../../../" },
419  };
420  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
421    EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" +
422              test_cases[i].path,
423              GetWebShortcutFilename(GURL(test_cases[i].url)).value()) <<
424        " while testing " << test_cases[i].url;
425  }
426}
427
428TEST(ShellIntegrationTest, GetDesktopFileContents) {
429  const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
430  const struct {
431    const char* url;
432    const char* title;
433    const char* icon_name;
434    const char* categories;
435    bool nodisplay;
436    const char* expected_output;
437  } test_cases[] = {
438    // Real-world case.
439    { "http://gmail.com",
440      "GMail",
441      "chrome-http__gmail.com",
442      "",
443      false,
444
445      "#!/usr/bin/env xdg-open\n"
446      "[Desktop Entry]\n"
447      "Version=1.0\n"
448      "Terminal=false\n"
449      "Type=Application\n"
450      "Name=GMail\n"
451      "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
452      "Icon=chrome-http__gmail.com\n"
453      "StartupWMClass=gmail.com\n"
454    },
455
456    // Make sure that empty icons are replaced by the chrome icon.
457    { "http://gmail.com",
458      "GMail",
459      "",
460      "",
461      false,
462
463      "#!/usr/bin/env xdg-open\n"
464      "[Desktop Entry]\n"
465      "Version=1.0\n"
466      "Terminal=false\n"
467      "Type=Application\n"
468      "Name=GMail\n"
469      "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
470#if defined(GOOGLE_CHROME_BUILD)
471      "Icon=google-chrome\n"
472#else
473      "Icon=chromium-browser\n"
474#endif
475      "StartupWMClass=gmail.com\n"
476    },
477
478    // Test adding categories and NoDisplay=true.
479    { "http://gmail.com",
480      "GMail",
481      "chrome-http__gmail.com",
482      "Graphics;Education;",
483      true,
484
485      "#!/usr/bin/env xdg-open\n"
486      "[Desktop Entry]\n"
487      "Version=1.0\n"
488      "Terminal=false\n"
489      "Type=Application\n"
490      "Name=GMail\n"
491      "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
492      "Icon=chrome-http__gmail.com\n"
493      "Categories=Graphics;Education;\n"
494      "NoDisplay=true\n"
495      "StartupWMClass=gmail.com\n"
496    },
497
498    // Now we're starting to be more evil...
499    { "http://evil.com/evil --join-the-b0tnet",
500      "Ownz0red\nExec=rm -rf /",
501      "chrome-http__evil.com_evil",
502      "",
503      false,
504
505      "#!/usr/bin/env xdg-open\n"
506      "[Desktop Entry]\n"
507      "Version=1.0\n"
508      "Terminal=false\n"
509      "Type=Application\n"
510      "Name=http://evil.com/evil%20--join-the-b0tnet\n"
511      "Exec=/opt/google/chrome/google-chrome "
512      "--app=http://evil.com/evil%20--join-the-b0tnet\n"
513      "Icon=chrome-http__evil.com_evil\n"
514      "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"
515    },
516    { "http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
517      "Innocent Title",
518      "chrome-http__evil.com_evil",
519      "",
520      false,
521
522      "#!/usr/bin/env xdg-open\n"
523      "[Desktop Entry]\n"
524      "Version=1.0\n"
525      "Terminal=false\n"
526      "Type=Application\n"
527      "Name=Innocent Title\n"
528      "Exec=/opt/google/chrome/google-chrome "
529      "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20"
530      // Note: $ is escaped as \$ within an arg to Exec, and then
531      // the \ is escaped as \\ as all strings in a Desktop file should
532      // be; finally, \\ becomes \\\\ when represented in a C++ string!
533      "-rf%20\\\\$HOME%20%3Eownz0red\"\n"
534      "Icon=chrome-http__evil.com_evil\n"
535      "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
536      "rm%20-rf%20$HOME%20%3Eownz0red\n"
537    },
538    { "http://evil.com/evil | cat `echo ownz0red` >/dev/null",
539      "Innocent Title",
540      "chrome-http__evil.com_evil",
541      "",
542      false,
543
544      "#!/usr/bin/env xdg-open\n"
545      "[Desktop Entry]\n"
546      "Version=1.0\n"
547      "Terminal=false\n"
548      "Type=Application\n"
549      "Name=Innocent Title\n"
550      "Exec=/opt/google/chrome/google-chrome "
551      "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red"
552      "%60%20%3E/dev/null\n"
553      "Icon=chrome-http__evil.com_evil\n"
554      "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
555      "%60%20%3E_dev_null\n"
556    },
557  };
558
559  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
560    SCOPED_TRACE(i);
561    EXPECT_EQ(
562        test_cases[i].expected_output,
563        GetDesktopFileContents(
564            kChromeExePath,
565            web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
566            GURL(test_cases[i].url),
567            std::string(),
568            base::ASCIIToUTF16(test_cases[i].title),
569            test_cases[i].icon_name,
570            base::FilePath(),
571            test_cases[i].categories,
572            test_cases[i].nodisplay));
573  }
574}
575
576TEST(ShellIntegrationTest, GetDesktopFileContentsAppList) {
577  const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
578  base::CommandLine command_line(kChromeExePath);
579  command_line.AppendSwitch("--show-app-list");
580  EXPECT_EQ(
581      "#!/usr/bin/env xdg-open\n"
582      "[Desktop Entry]\n"
583      "Version=1.0\n"
584      "Terminal=false\n"
585      "Type=Application\n"
586      "Name=Chrome App Launcher\n"
587      "Exec=/opt/google/chrome/google-chrome --show-app-list\n"
588      "Icon=chrome_app_list\n"
589      "Categories=Network;WebBrowser;\n"
590      "StartupWMClass=chrome-app-list\n",
591      GetDesktopFileContentsForCommand(
592          command_line,
593          "chrome-app-list",
594          GURL(),
595          base::ASCIIToUTF16("Chrome App Launcher"),
596          "chrome_app_list",
597          "Network;WebBrowser;",
598          false));
599}
600
601TEST(ShellIntegrationTest, GetDirectoryFileContents) {
602  const struct {
603    const char* title;
604    const char* icon_name;
605    const char* expected_output;
606  } test_cases[] = {
607    // Real-world case.
608    { "Chrome Apps",
609      "chrome-apps",
610
611      "[Desktop Entry]\n"
612      "Version=1.0\n"
613      "Type=Directory\n"
614      "Name=Chrome Apps\n"
615      "Icon=chrome-apps\n"
616    },
617
618    // Make sure that empty icons are replaced by the chrome icon.
619    { "Chrome Apps",
620      "",
621
622      "[Desktop Entry]\n"
623      "Version=1.0\n"
624      "Type=Directory\n"
625      "Name=Chrome Apps\n"
626#if defined(GOOGLE_CHROME_BUILD)
627      "Icon=google-chrome\n"
628#else
629      "Icon=chromium-browser\n"
630#endif
631    },
632  };
633
634  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
635    SCOPED_TRACE(i);
636    EXPECT_EQ(test_cases[i].expected_output,
637              GetDirectoryFileContents(base::ASCIIToUTF16(test_cases[i].title),
638                                       test_cases[i].icon_name));
639  }
640}
641
642}  // namespace shell_integration_linux
643