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