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