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