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