| OLD | NEW |
| (Empty) |
| 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 <cstdlib> | |
| 8 #include <map> | |
| 9 | |
| 10 #include "base/file_util.h" | |
| 11 #include "base/files/file_path.h" | |
| 12 #include "base/files/scoped_temp_dir.h" | |
| 13 #include "base/message_loop.h" | |
| 14 #include "base/stl_util.h" | |
| 15 #include "base/string_util.h" | |
| 16 #include "base/utf_string_conversions.h" | |
| 17 #include "chrome/browser/web_applications/web_app.h" | |
| 18 #include "chrome/common/chrome_constants.h" | |
| 19 #include "content/public/test/test_browser_thread.h" | |
| 20 #include "googleurl/src/gurl.h" | |
| 21 #include "testing/gtest/include/gtest/gtest.h" | |
| 22 | |
| 23 #if defined(OS_POSIX) && !defined(OS_MACOSX) | |
| 24 #include "base/environment.h" | |
| 25 #include "chrome/browser/shell_integration_linux.h" | |
| 26 #endif | |
| 27 | |
| 28 #define FPL FILE_PATH_LITERAL | |
| 29 | |
| 30 using content::BrowserThread; | |
| 31 | |
| 32 #if defined(OS_POSIX) && !defined(OS_MACOSX) | |
| 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 // Allows you to change the real environment, but reverts changes upon | |
| 71 // destruction. | |
| 72 class ScopedEnvironment { | |
| 73 public: | |
| 74 ScopedEnvironment() {} | |
| 75 | |
| 76 ~ScopedEnvironment() { | |
| 77 for (std::map<std::string, std::string>::const_iterator | |
| 78 it = old_variables_.begin(); it != old_variables_.end(); ++it) { | |
| 79 if (it->second.empty()) { | |
| 80 unsetenv(it->first.c_str()); | |
| 81 } else { | |
| 82 setenv(it->first.c_str(), it->second.c_str(), 1); | |
| 83 } | |
| 84 } | |
| 85 } | |
| 86 | |
| 87 void Set(const std::string& name, const std::string& value) { | |
| 88 if (!ContainsKey(old_variables_, name)) { | |
| 89 const char* value = getenv(name.c_str()); | |
| 90 if (value != NULL) { | |
| 91 old_variables_[name] = value; | |
| 92 } else { | |
| 93 old_variables_[name] = std::string(); | |
| 94 } | |
| 95 } | |
| 96 setenv(name.c_str(), value.c_str(), 1); | |
| 97 } | |
| 98 | |
| 99 private: | |
| 100 // Map from name to original value, or the empty string if there was no | |
| 101 // previous value. | |
| 102 std::map<std::string, std::string> old_variables_; | |
| 103 | |
| 104 DISALLOW_COPY_AND_ASSIGN(ScopedEnvironment); | |
| 105 }; | |
| 106 | |
| 107 } // namespace | |
| 108 | |
| 109 TEST(ShellIntegrationTest, GetDesktopShortcutTemplate) { | |
| 110 #if defined(GOOGLE_CHROME_BUILD) | |
| 111 const char kTemplateFilename[] = "google-chrome.desktop"; | |
| 112 #else // CHROMIUM_BUILD | |
| 113 const char kTemplateFilename[] = "chromium-browser.desktop"; | |
| 114 #endif | |
| 115 | |
| 116 const char kTestData1[] = "a magical testing string"; | |
| 117 const char kTestData2[] = "a different testing string"; | |
| 118 | |
| 119 MessageLoop message_loop; | |
| 120 content::TestBrowserThread file_thread(BrowserThread::FILE, &message_loop); | |
| 121 | |
| 122 { | |
| 123 base::ScopedTempDir temp_dir; | |
| 124 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| 125 | |
| 126 MockEnvironment env; | |
| 127 env.Set("XDG_DATA_HOME", temp_dir.path().value()); | |
| 128 ASSERT_TRUE(file_util::WriteFile( | |
| 129 temp_dir.path().AppendASCII(kTemplateFilename), | |
| 130 kTestData1, strlen(kTestData1))); | |
| 131 std::string contents; | |
| 132 ASSERT_TRUE(ShellIntegrationLinux::GetDesktopShortcutTemplate(&env, | |
| 133 &contents)); | |
| 134 EXPECT_EQ(kTestData1, contents); | |
| 135 } | |
| 136 | |
| 137 { | |
| 138 base::ScopedTempDir temp_dir; | |
| 139 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| 140 | |
| 141 MockEnvironment env; | |
| 142 env.Set("XDG_DATA_DIRS", temp_dir.path().value()); | |
| 143 ASSERT_TRUE(file_util::CreateDirectory( | |
| 144 temp_dir.path().AppendASCII("applications"))); | |
| 145 ASSERT_TRUE(file_util::WriteFile( | |
| 146 temp_dir.path().AppendASCII("applications") | |
| 147 .AppendASCII(kTemplateFilename), | |
| 148 kTestData2, strlen(kTestData2))); | |
| 149 std::string contents; | |
| 150 ASSERT_TRUE(ShellIntegrationLinux::GetDesktopShortcutTemplate(&env, | |
| 151 &contents)); | |
| 152 EXPECT_EQ(kTestData2, contents); | |
| 153 } | |
| 154 | |
| 155 { | |
| 156 base::ScopedTempDir temp_dir; | |
| 157 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
| 158 | |
| 159 MockEnvironment env; | |
| 160 env.Set("XDG_DATA_DIRS", temp_dir.path().value() + ":" + | |
| 161 temp_dir.path().AppendASCII("applications").value()); | |
| 162 ASSERT_TRUE(file_util::CreateDirectory( | |
| 163 temp_dir.path().AppendASCII("applications"))); | |
| 164 ASSERT_TRUE(file_util::WriteFile( | |
| 165 temp_dir.path().AppendASCII(kTemplateFilename), | |
| 166 kTestData1, strlen(kTestData1))); | |
| 167 ASSERT_TRUE(file_util::WriteFile( | |
| 168 temp_dir.path().AppendASCII("applications") | |
| 169 .AppendASCII(kTemplateFilename), | |
| 170 kTestData2, strlen(kTestData2))); | |
| 171 std::string contents; | |
| 172 ASSERT_TRUE(ShellIntegrationLinux::GetDesktopShortcutTemplate(&env, | |
| 173 &contents)); | |
| 174 EXPECT_EQ(kTestData1, contents); | |
| 175 } | |
| 176 } | |
| 177 | |
| 178 TEST(ShellIntegrationTest, GetWebShortcutFilename) { | |
| 179 const struct { | |
| 180 const base::FilePath::CharType* path; | |
| 181 const char* url; | |
| 182 } test_cases[] = { | |
| 183 { FPL("http___foo_.desktop"), "http://foo" }, | |
| 184 { FPL("http___foo_bar_.desktop"), "http://foo/bar/" }, | |
| 185 { FPL("http___foo_bar_a=b&c=d.desktop"), "http://foo/bar?a=b&c=d" }, | |
| 186 | |
| 187 // Now we're starting to be more evil... | |
| 188 { FPL("http___foo_.desktop"), "http://foo/bar/baz/../../../../../" }, | |
| 189 { FPL("http___foo_.desktop"), "http://foo/bar/././../baz/././../" }, | |
| 190 { FPL("http___.._.desktop"), "http://../../../../" }, | |
| 191 }; | |
| 192 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) { | |
| 193 EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" + | |
| 194 test_cases[i].path, | |
| 195 ShellIntegrationLinux::GetWebShortcutFilename( | |
| 196 GURL(test_cases[i].url)).value()) << | |
| 197 " while testing " << test_cases[i].url; | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 TEST(ShellIntegrationTest, GetDesktopFileContents) { | |
| 202 const struct { | |
| 203 const char* url; | |
| 204 const char* title; | |
| 205 const char* icon_name; | |
| 206 const char* template_contents; | |
| 207 const char* expected_output; | |
| 208 } test_cases[] = { | |
| 209 // Dumb case. | |
| 210 { "ignored", "ignored", "ignored", "", "#!/usr/bin/env xdg-open\n" }, | |
| 211 | |
| 212 // Real-world case. | |
| 213 { "http://gmail.com", | |
| 214 "GMail", | |
| 215 "chrome-http__gmail.com", | |
| 216 | |
| 217 "[Desktop Entry]\n" | |
| 218 "Version=1.0\n" | |
| 219 "Encoding=UTF-8\n" | |
| 220 "Name=Google Chrome\n" | |
| 221 "GenericName=Web Browser\n" | |
| 222 "Comment=The web browser from Google\n" | |
| 223 "Exec=/opt/google/chrome/google-chrome %U\n" | |
| 224 "Terminal=false\n" | |
| 225 "Icon=/opt/google/chrome/product_logo_48.png\n" | |
| 226 "Type=Application\n" | |
| 227 "Categories=Application;Network;WebBrowser;\n" | |
| 228 "MimeType=text/html;text/xml;application/xhtml_xml;\n" | |
| 229 "X-Ayatana-Desktop-Shortcuts=NewWindow;\n" | |
| 230 "\n" | |
| 231 "[NewWindow Shortcut Group]\n" | |
| 232 "Name=Open New Window\n" | |
| 233 "Exec=/opt/google/chrome/google-chrome\n" | |
| 234 "TargetEnvironment=Unity\n", | |
| 235 | |
| 236 "#!/usr/bin/env xdg-open\n" | |
| 237 "[Desktop Entry]\n" | |
| 238 "Version=1.0\n" | |
| 239 "Encoding=UTF-8\n" | |
| 240 "Name=GMail\n" | |
| 241 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" | |
| 242 "Terminal=false\n" | |
| 243 "Icon=chrome-http__gmail.com\n" | |
| 244 "Type=Application\n" | |
| 245 "Categories=Application;Network;WebBrowser;\n" | |
| 246 #if !defined(USE_AURA) | |
| 247 // Aura Chrome does not (yet) set WMClass, so we only expect | |
| 248 // StartupWMClass on non-Aura builds. | |
| 249 "StartupWMClass=gmail.com\n" | |
| 250 #endif | |
| 251 }, | |
| 252 | |
| 253 // Make sure we don't insert duplicate shebangs. | |
| 254 { "http://gmail.com", | |
| 255 "GMail", | |
| 256 "chrome-http__gmail.com", | |
| 257 | |
| 258 "#!/some/shebang\n" | |
| 259 "[Desktop Entry]\n" | |
| 260 "Name=Google Chrome\n" | |
| 261 "Exec=/opt/google/chrome/google-chrome %U\n", | |
| 262 | |
| 263 "#!/usr/bin/env xdg-open\n" | |
| 264 "[Desktop Entry]\n" | |
| 265 "Name=GMail\n" | |
| 266 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" | |
| 267 "Icon=chrome-http__gmail.com\n" | |
| 268 #if !defined(USE_AURA) | |
| 269 // Aura Chrome does not (yet) set WMClass, so we only expect | |
| 270 // StartupWMClass on non-Aura builds. | |
| 271 "StartupWMClass=gmail.com\n" | |
| 272 #endif | |
| 273 }, | |
| 274 | |
| 275 // Make sure i18n-ed names and other fields are removed. | |
| 276 { "http://gmail.com", | |
| 277 "GMail", | |
| 278 "chrome-http__gmail.com", | |
| 279 | |
| 280 "[Desktop Entry]\n" | |
| 281 "Name=Google Chrome\n" | |
| 282 "Name[en_AU]=Google Chrome\n" | |
| 283 "Name[pl]=Google Chrome\n" | |
| 284 "GenericName=Web Browser\n" | |
| 285 "GenericName[en_AU]=Web Browser\n" | |
| 286 "GenericName[pl]=Navegador Web\n" | |
| 287 "Exec=/opt/google/chrome/google-chrome %U\n" | |
| 288 "Comment[en_AU]=Some comment.\n" | |
| 289 "Comment[pl]=Jakis komentarz.\n", | |
| 290 | |
| 291 "#!/usr/bin/env xdg-open\n" | |
| 292 "[Desktop Entry]\n" | |
| 293 "Name=GMail\n" | |
| 294 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" | |
| 295 "Icon=chrome-http__gmail.com\n" | |
| 296 #if !defined(USE_AURA) | |
| 297 // Aura Chrome does not (yet) set WMClass, so we only expect | |
| 298 // StartupWMClass on non-Aura builds. | |
| 299 "StartupWMClass=gmail.com\n" | |
| 300 #endif | |
| 301 }, | |
| 302 | |
| 303 // Make sure that empty icons are replaced by the chrome icon. | |
| 304 { "http://gmail.com", | |
| 305 "GMail", | |
| 306 "", | |
| 307 | |
| 308 "[Desktop Entry]\n" | |
| 309 "Name=Google Chrome\n" | |
| 310 "Exec=/opt/google/chrome/google-chrome %U\n" | |
| 311 "Comment[pl]=Jakis komentarz.\n" | |
| 312 "Icon=/opt/google/chrome/product_logo_48.png\n", | |
| 313 | |
| 314 "#!/usr/bin/env xdg-open\n" | |
| 315 "[Desktop Entry]\n" | |
| 316 "Name=GMail\n" | |
| 317 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n" | |
| 318 "Icon=/opt/google/chrome/product_logo_48.png\n" | |
| 319 #if !defined(USE_AURA) | |
| 320 // Aura Chrome does not (yet) set WMClass, so we only expect | |
| 321 // StartupWMClass on non-Aura builds. | |
| 322 "StartupWMClass=gmail.com\n" | |
| 323 #endif | |
| 324 }, | |
| 325 | |
| 326 // Now we're starting to be more evil... | |
| 327 { "http://evil.com/evil --join-the-b0tnet", | |
| 328 "Ownz0red\nExec=rm -rf /", | |
| 329 "chrome-http__evil.com_evil", | |
| 330 | |
| 331 "[Desktop Entry]\n" | |
| 332 "Name=Google Chrome\n" | |
| 333 "Exec=/opt/google/chrome/google-chrome %U\n", | |
| 334 | |
| 335 "#!/usr/bin/env xdg-open\n" | |
| 336 "[Desktop Entry]\n" | |
| 337 "Name=http://evil.com/evil%20--join-the-b0tnet\n" | |
| 338 "Exec=/opt/google/chrome/google-chrome " | |
| 339 "--app=http://evil.com/evil%20--join-the-b0tnet\n" | |
| 340 "Icon=chrome-http__evil.com_evil\n" | |
| 341 #if !defined(USE_AURA) | |
| 342 // Aura Chrome does not (yet) set WMClass, so we only expect | |
| 343 // StartupWMClass on non-Aura builds. | |
| 344 "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n" | |
| 345 #endif | |
| 346 }, | |
| 347 { "http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red", | |
| 348 "Innocent Title", | |
| 349 "chrome-http__evil.com_evil", | |
| 350 | |
| 351 "[Desktop Entry]\n" | |
| 352 "Name=Google Chrome\n" | |
| 353 "Exec=/opt/google/chrome/google-chrome %U\n", | |
| 354 | |
| 355 "#!/usr/bin/env xdg-open\n" | |
| 356 "[Desktop Entry]\n" | |
| 357 "Name=Innocent Title\n" | |
| 358 "Exec=/opt/google/chrome/google-chrome " | |
| 359 "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20" | |
| 360 // Note: $ is escaped as \$ within an arg to Exec, and then | |
| 361 // the \ is escaped as \\ as all strings in a Desktop file should | |
| 362 // be; finally, \\ becomes \\\\ when represented in a C++ string! | |
| 363 "-rf%20\\\\$HOME%20%3Eownz0red\"\n" | |
| 364 "Icon=chrome-http__evil.com_evil\n" | |
| 365 #if !defined(USE_AURA) | |
| 366 // Aura Chrome does not (yet) set WMClass, so we only expect | |
| 367 // StartupWMClass on non-Aura builds. | |
| 368 "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20" | |
| 369 "rm%20-rf%20$HOME%20%3Eownz0red\n" | |
| 370 #endif | |
| 371 }, | |
| 372 { "http://evil.com/evil | cat `echo ownz0red` >/dev/null", | |
| 373 "Innocent Title", | |
| 374 "chrome-http__evil.com_evil", | |
| 375 | |
| 376 "[Desktop Entry]\n" | |
| 377 "Name=Google Chrome\n" | |
| 378 "Exec=/opt/google/chrome/google-chrome %U\n", | |
| 379 | |
| 380 "#!/usr/bin/env xdg-open\n" | |
| 381 "[Desktop Entry]\n" | |
| 382 "Name=Innocent Title\n" | |
| 383 "Exec=/opt/google/chrome/google-chrome " | |
| 384 "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red" | |
| 385 "%60%20%3E/dev/null\n" | |
| 386 "Icon=chrome-http__evil.com_evil\n" | |
| 387 #if !defined(USE_AURA) | |
| 388 // Aura Chrome does not (yet) set WMClass, so we only expect | |
| 389 // StartupWMClass on non-Aura builds. | |
| 390 "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red" | |
| 391 "%60%20%3E_dev_null\n" | |
| 392 #endif | |
| 393 }, | |
| 394 }; | |
| 395 | |
| 396 // Set the language to en_AU. This causes glib to copy the en_AU localized | |
| 397 // strings into the shortcut file. (We want to test that they are removed.) | |
| 398 ScopedEnvironment env; | |
| 399 env.Set("LC_ALL", "en_AU.UTF-8"); | |
| 400 env.Set("LANGUAGE", "en_AU.UTF-8"); | |
| 401 | |
| 402 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); i++) { | |
| 403 SCOPED_TRACE(i); | |
| 404 EXPECT_EQ( | |
| 405 test_cases[i].expected_output, | |
| 406 ShellIntegrationLinux::GetDesktopFileContents( | |
| 407 test_cases[i].template_contents, | |
| 408 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)), | |
| 409 GURL(test_cases[i].url), | |
| 410 "", | |
| 411 base::FilePath(), | |
| 412 ASCIIToUTF16(test_cases[i].title), | |
| 413 test_cases[i].icon_name, | |
| 414 base::FilePath())); | |
| 415 } | |
| 416 } | |
| 417 #endif | |
| OLD | NEW |