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