Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2011 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 <map>
      8 
      9 #include "base/file_path.h"
     10 #include "base/file_util.h"
     11 #include "base/memory/scoped_temp_dir.h"
     12 #include "base/message_loop.h"
     13 #include "base/stl_util-inl.h"
     14 #include "base/string_util.h"
     15 #include "base/utf_string_conversions.h"
     16 #include "chrome/browser/web_applications/web_app.h"
     17 #include "chrome/common/chrome_constants.h"
     18 #include "chrome/common/chrome_paths_internal.h"
     19 #include "content/browser/browser_thread.h"
     20 #include "googleurl/src/gurl.h"
     21 #include "testing/gtest/include/gtest/gtest.h"
     22 
     23 #if defined(OS_WIN)
     24 #include "chrome/installer/util/browser_distribution.h"
     25 #elif defined(OS_LINUX)
     26 #include "base/environment.h"
     27 #endif  // defined(OS_LINUX)
     28 
     29 #define FPL FILE_PATH_LITERAL
     30 
     31 #if defined(OS_LINUX)
     32 namespace {
     33 
     34 // Provides mock environment variables values based on a stored map.
     35 class MockEnvironment : public base::Environment {
     36  public:
     37   MockEnvironment() {}
     38 
     39   void Set(const std::string& name, const std::string& value) {
     40     variables_[name] = value;
     41   }
     42 
     43   virtual bool GetVar(const char* variable_name, std::string* result) {
     44     if (ContainsKey(variables_, variable_name)) {
     45       *result = variables_[variable_name];
     46       return true;
     47     }
     48 
     49     return false;
     50   }
     51 
     52   virtual bool SetVar(const char* variable_name, const std::string& new_value) {
     53     ADD_FAILURE();
     54     return false;
     55   }
     56 
     57   virtual bool UnSetVar(const char* variable_name) {
     58     ADD_FAILURE();
     59     return false;
     60   }
     61 
     62  private:
     63   std::map<std::string, std::string> variables_;
     64 
     65   DISALLOW_COPY_AND_ASSIGN(MockEnvironment);
     66 };
     67 
     68 }  // namespace
     69 
     70 TEST(ShellIntegrationTest, GetDesktopShortcutTemplate) {
     71 #if defined(GOOGLE_CHROME_BUILD)
     72   const char kTemplateFilename[] = "google-chrome.desktop";
     73 #else  // CHROMIUM_BUILD
     74   const char kTemplateFilename[] = "chromium-browser.desktop";
     75 #endif
     76 
     77   const char kTestData1[] = "a magical testing string";
     78   const char kTestData2[] = "a different testing string";
     79 
     80   MessageLoop message_loop;
     81   BrowserThread file_thread(BrowserThread::FILE, &message_loop);
     82 
     83   {
     84     ScopedTempDir temp_dir;
     85     ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
     86 
     87     MockEnvironment env;
     88     env.Set("XDG_DATA_HOME", temp_dir.path().value());
     89     ASSERT_TRUE(file_util::WriteFile(
     90         temp_dir.path().AppendASCII(kTemplateFilename),
     91         kTestData1, strlen(kTestData1)));
     92     std::string contents;
     93     ASSERT_TRUE(ShellIntegration::GetDesktopShortcutTemplate(&env,
     94                                                              &contents));
     95     EXPECT_EQ(kTestData1, contents);
     96   }
     97 
     98   {
     99     ScopedTempDir temp_dir;
    100     ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    101 
    102     MockEnvironment env;
    103     env.Set("XDG_DATA_DIRS", temp_dir.path().value());
    104     ASSERT_TRUE(file_util::CreateDirectory(
    105         temp_dir.path().AppendASCII("applications")));
    106     ASSERT_TRUE(file_util::WriteFile(
    107         temp_dir.path().AppendASCII("applications")
    108             .AppendASCII(kTemplateFilename),
    109         kTestData2, strlen(kTestData2)));
    110     std::string contents;
    111     ASSERT_TRUE(ShellIntegration::GetDesktopShortcutTemplate(&env,
    112                                                              &contents));
    113     EXPECT_EQ(kTestData2, contents);
    114   }
    115 
    116   {
    117     ScopedTempDir temp_dir;
    118     ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    119 
    120     MockEnvironment env;
    121     env.Set("XDG_DATA_DIRS", temp_dir.path().value() + ":" +
    122                    temp_dir.path().AppendASCII("applications").value());
    123     ASSERT_TRUE(file_util::CreateDirectory(
    124         temp_dir.path().AppendASCII("applications")));
    125     ASSERT_TRUE(file_util::WriteFile(
    126         temp_dir.path().AppendASCII(kTemplateFilename),
    127         kTestData1, strlen(kTestData1)));
    128     ASSERT_TRUE(file_util::WriteFile(
    129         temp_dir.path().AppendASCII("applications")
    130             .AppendASCII(kTemplateFilename),
    131         kTestData2, strlen(kTestData2)));
    132     std::string contents;
    133     ASSERT_TRUE(ShellIntegration::GetDesktopShortcutTemplate(&env,
    134                                                              &contents));
    135     EXPECT_EQ(kTestData1, contents);
    136   }
    137 }
    138 
    139 TEST(ShellIntegrationTest, GetDesktopShortcutFilename) {
    140   const struct {
    141     const FilePath::CharType* path;
    142     const char* url;
    143   } test_cases[] = {
    144     { FPL("http___foo_.desktop"), "http://foo" },
    145     { FPL("http___foo_bar_.desktop"), "http://foo/bar/" },
    146     { FPL("http___foo_bar_a=b&c=d.desktop"), "http://foo/bar?a=b&c=d" },
    147 
    148     // Now we're starting to be more evil...
    149     { FPL("http___foo_.desktop"), "http://foo/bar/baz/../../../../../" },
    150     { FPL("http___foo_.desktop"), "http://foo/bar/././../baz/././../" },
    151     { FPL("http___.._.desktop"), "http://../../../../" },
    152   };
    153   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
    154     EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" +
    155               test_cases[i].path,
    156               ShellIntegration::GetDesktopShortcutFilename(
    157                   GURL(test_cases[i].url)).value()) <<
    158         " while testing " << test_cases[i].url;
    159   }
    160 }
    161 
    162 TEST(ShellIntegrationTest, GetDesktopFileContents) {
    163   const struct {
    164     const char* url;
    165     const char* title;
    166     const char* icon_name;
    167     const char* template_contents;
    168     const char* expected_output;
    169   } test_cases[] = {
    170     // Dumb case.
    171     { "ignored", "ignored", "ignored", "", "#!/usr/bin/env xdg-open\n" },
    172 
    173     // Real-world case.
    174     { "http://gmail.com",
    175       "GMail",
    176       "chrome-http__gmail.com",
    177 
    178       "[Desktop Entry]\n"
    179       "Version=1.0\n"
    180       "Encoding=UTF-8\n"
    181       "Name=Google Chrome\n"
    182       "Comment=The web browser from Google\n"
    183       "Exec=/opt/google/chrome/google-chrome %U\n"
    184       "Terminal=false\n"
    185       "Icon=/opt/google/chrome/product_logo_48.png\n"
    186       "Type=Application\n"
    187       "Categories=Application;Network;WebBrowser;\n"
    188       "MimeType=text/html;text/xml;application/xhtml_xml;\n",
    189 
    190       "#!/usr/bin/env xdg-open\n"
    191       "[Desktop Entry]\n"
    192       "Version=1.0\n"
    193       "Encoding=UTF-8\n"
    194       "Name=GMail\n"
    195       "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
    196       "Terminal=false\n"
    197       "Icon=chrome-http__gmail.com\n"
    198       "Type=Application\n"
    199       "Categories=Application;Network;WebBrowser;\n"
    200       "StartupWMClass=gmail.com\n"
    201     },
    202 
    203     // Make sure we don't insert duplicate shebangs.
    204     { "http://gmail.com",
    205       "GMail",
    206       "chrome-http__gmail.com",
    207 
    208       "#!/some/shebang\n"
    209       "Name=Google Chrome\n"
    210       "Exec=/opt/google/chrome/google-chrome %U\n",
    211 
    212       "#!/usr/bin/env xdg-open\n"
    213       "Name=GMail\n"
    214       "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
    215       "StartupWMClass=gmail.com\n"
    216     },
    217 
    218     // Make sure i18n-ed comments are removed.
    219     { "http://gmail.com",
    220       "GMail",
    221       "chrome-http__gmail.com",
    222 
    223       "Name=Google Chrome\n"
    224       "Exec=/opt/google/chrome/google-chrome %U\n"
    225       "Comment[pl]=Jakis komentarz.\n",
    226 
    227       "#!/usr/bin/env xdg-open\n"
    228       "Name=GMail\n"
    229       "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
    230       "StartupWMClass=gmail.com\n"
    231     },
    232 
    233     // Make sure that empty icons are replaced by the chrome icon.
    234     { "http://gmail.com",
    235       "GMail",
    236       "",
    237 
    238       "Name=Google Chrome\n"
    239       "Exec=/opt/google/chrome/google-chrome %U\n"
    240       "Comment[pl]=Jakis komentarz.\n"
    241       "Icon=/opt/google/chrome/product_logo_48.png\n",
    242 
    243       "#!/usr/bin/env xdg-open\n"
    244       "Name=GMail\n"
    245       "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
    246       "Icon=/opt/google/chrome/product_logo_48.png\n"
    247       "StartupWMClass=gmail.com\n"
    248     },
    249 
    250     // Now we're starting to be more evil...
    251     { "http://evil.com/evil --join-the-b0tnet",
    252       "Ownz0red\nExec=rm -rf /",
    253       "chrome-http__evil.com_evil",
    254 
    255       "Name=Google Chrome\n"
    256       "Exec=/opt/google/chrome/google-chrome %U\n",
    257 
    258       "#!/usr/bin/env xdg-open\n"
    259       "Name=http://evil.com/evil%20--join-the-b0tnet\n"
    260       "Exec=/opt/google/chrome/google-chrome "
    261       "--app=http://evil.com/evil%20--join-the-b0tnet\n"
    262       "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"
    263     },
    264     { "http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
    265       "Innocent Title",
    266       "chrome-http__evil.com_evil",
    267 
    268       "Name=Google Chrome\n"
    269       "Exec=/opt/google/chrome/google-chrome %U\n",
    270 
    271       "#!/usr/bin/env xdg-open\n"
    272       "Name=Innocent Title\n"
    273       "Exec=/opt/google/chrome/google-chrome "
    274       "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20"
    275       // Note: $ is escaped as \$ within an arg to Exec, and then
    276       // the \ is escaped as \\ as all strings in a Desktop file should
    277       // be; finally, \\ becomes \\\\ when represented in a C++ string!
    278       "-rf%20\\\\$HOME%20%3Eownz0red\"\n"
    279       "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
    280       "rm%20-rf%20$HOME%20%3Eownz0red\n"
    281     },
    282     { "http://evil.com/evil | cat `echo ownz0red` >/dev/null",
    283       "Innocent Title",
    284       "chrome-http__evil.com_evil",
    285 
    286       "Name=Google Chrome\n"
    287       "Exec=/opt/google/chrome/google-chrome %U\n",
    288 
    289       "#!/usr/bin/env xdg-open\n"
    290       "Name=Innocent Title\n"
    291       "Exec=/opt/google/chrome/google-chrome "
    292       "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red"
    293       "%60%20%3E/dev/null\n"
    294       "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
    295       "%60%20%3E_dev_null\n"
    296     },
    297   };
    298   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) {
    299     SCOPED_TRACE(i);
    300     EXPECT_EQ(
    301         test_cases[i].expected_output,
    302         ShellIntegration::GetDesktopFileContents(
    303             test_cases[i].template_contents,
    304             web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
    305             GURL(test_cases[i].url),
    306             "",
    307             ASCIIToUTF16(test_cases[i].title),
    308             test_cases[i].icon_name));
    309   }
    310 }
    311 #elif defined(OS_WIN)
    312 TEST(ShellIntegrationTest, GetChromiumAppIdTest) {
    313   // Empty profile path should get chrome::kBrowserAppID
    314   FilePath empty_path;
    315   EXPECT_EQ(BrowserDistribution::GetDistribution()->GetBrowserAppId(),
    316             ShellIntegration::GetChromiumAppId(empty_path));
    317 
    318   // Default profile path should get chrome::kBrowserAppID
    319   FilePath default_user_data_dir;
    320   chrome::GetDefaultUserDataDirectory(&default_user_data_dir);
    321   FilePath default_profile_path =
    322       default_user_data_dir.AppendASCII(chrome::kNotSignedInProfile);
    323   EXPECT_EQ(BrowserDistribution::GetDistribution()->GetBrowserAppId(),
    324             ShellIntegration::GetChromiumAppId(default_profile_path));
    325 
    326   // Non-default profile path should get chrome::kBrowserAppID joined with
    327   // profile info.
    328   FilePath profile_path(FILE_PATH_LITERAL("root"));
    329   profile_path = profile_path.Append(FILE_PATH_LITERAL("udd"));
    330   profile_path = profile_path.Append(FILE_PATH_LITERAL("User Data - Test"));
    331   EXPECT_EQ(BrowserDistribution::GetDistribution()->GetBrowserAppId() +
    332             L".udd.UserDataTest",
    333             ShellIntegration::GetChromiumAppId(profile_path));
    334 }
    335 #endif
    336