[M120 Migration]Fix for crash during chrome exit
[platform/framework/web/chromium-efl.git] / chrome / browser / shell_integration_linux_unittest.cc
1 // Copyright 2013 The Chromium Authors
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 <stddef.h>
8
9 #include <algorithm>
10 #include <cstdlib>
11 #include <map>
12 #include <vector>
13
14 #include "base/base_paths.h"
15 #include "base/command_line.h"
16 #include "base/containers/contains.h"
17 #include "base/environment.h"
18 #include "base/files/file_path.h"
19 #include "base/files/file_util.h"
20 #include "base/files/scoped_temp_dir.h"
21 #include "base/strings/string_piece.h"
22 #include "base/strings/string_util.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/test/scoped_path_override.h"
25 #include "build/branding_buildflags.h"
26 #include "chrome/browser/web_applications/os_integration/web_app_shortcut.h"
27 #include "chrome/browser/web_applications/web_app_helpers.h"
28 #include "chrome/common/chrome_constants.h"
29 #include "components/services/app_service/public/cpp/file_handler.h"
30 #include "components/webapps/common/web_app_id.h"
31 #include "content/public/test/browser_task_environment.h"
32 #include "testing/gmock/include/gmock/gmock.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 #include "ui/ozone/public/ozone_platform.h"
35 #include "url/gurl.h"
36
37 using ::testing::ElementsAre;
38
39 namespace shell_integration_linux {
40
41 namespace {
42
43 // Provides mock environment variables values based on a stored map.
44 class MockEnvironment : public base::Environment {
45  public:
46   MockEnvironment() {}
47
48   MockEnvironment(const MockEnvironment&) = delete;
49   MockEnvironment& operator=(const MockEnvironment&) = delete;
50
51   void Set(base::StringPiece name, const std::string& value) {
52     variables_[std::string(name)] = value;
53   }
54
55   bool GetVar(base::StringPiece variable_name, std::string* result) override {
56     if (base::Contains(variables_, std::string(variable_name))) {
57       *result = variables_[std::string(variable_name)];
58       return true;
59     }
60
61     return false;
62   }
63
64   bool SetVar(base::StringPiece variable_name,
65               const std::string& new_value) override {
66     ADD_FAILURE();
67     return false;
68   }
69
70   bool UnSetVar(base::StringPiece variable_name) override {
71     ADD_FAILURE();
72     return false;
73   }
74
75  private:
76   std::map<std::string, std::string> variables_;
77 };
78
79 // This helps EXPECT_THAT(..., ElementsAre(...)) print out more meaningful
80 // failure messages.
81 std::vector<std::string> FilePathsToStrings(
82     const std::vector<base::FilePath>& paths) {
83   std::vector<std::string> values;
84   for (const auto& path : paths)
85     values.push_back(path.value());
86   return values;
87 }
88
89 }  // namespace
90
91 TEST(ShellIntegrationTest, GetDataWriteLocation) {
92   content::BrowserTaskEnvironment task_environment;
93
94   // Test that it returns $XDG_DATA_HOME.
95   {
96     MockEnvironment env;
97     base::ScopedPathOverride home_override(base::DIR_HOME,
98                                            base::FilePath("/home/user"),
99                                            true /* absolute? */,
100                                            false /* create? */);
101     env.Set("XDG_DATA_HOME", "/user/path");
102     base::FilePath path = GetDataWriteLocation(&env);
103     EXPECT_EQ("/user/path", path.value());
104   }
105
106   // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
107   {
108     MockEnvironment env;
109     base::ScopedPathOverride home_override(base::DIR_HOME,
110                                            base::FilePath("/home/user"),
111                                            true /* absolute? */,
112                                            false /* create? */);
113     base::FilePath path = GetDataWriteLocation(&env);
114     EXPECT_EQ("/home/user/.local/share", path.value());
115   }
116 }
117
118 TEST(ShellIntegrationTest, GetDataSearchLocations) {
119   content::BrowserTaskEnvironment task_environment;
120
121   // Test that it returns $XDG_DATA_HOME + $XDG_DATA_DIRS.
122   {
123     MockEnvironment env;
124     base::ScopedPathOverride home_override(base::DIR_HOME,
125                                            base::FilePath("/home/user"),
126                                            true /* absolute? */,
127                                            false /* create? */);
128     env.Set("XDG_DATA_HOME", "/user/path");
129     env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
130     EXPECT_THAT(
131         FilePathsToStrings(GetDataSearchLocations(&env)),
132         ElementsAre("/user/path",
133                     "/system/path/1",
134                     "/system/path/2"));
135   }
136
137   // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
138   {
139     MockEnvironment env;
140     base::ScopedPathOverride home_override(base::DIR_HOME,
141                                            base::FilePath("/home/user"),
142                                            true /* absolute? */,
143                                            false /* create? */);
144     env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
145     EXPECT_THAT(
146         FilePathsToStrings(GetDataSearchLocations(&env)),
147         ElementsAre("/home/user/.local/share",
148                     "/system/path/1",
149                     "/system/path/2"));
150   }
151
152   // Test that if neither $XDG_DATA_HOME nor $HOME are specified, it still
153   // succeeds.
154   {
155     MockEnvironment env;
156     env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
157     std::vector<std::string> results =
158         FilePathsToStrings(GetDataSearchLocations(&env));
159     ASSERT_EQ(3U, results.size());
160     EXPECT_FALSE(results[0].empty());
161     EXPECT_EQ("/system/path/1", results[1]);
162     EXPECT_EQ("/system/path/2", results[2]);
163   }
164
165   // Test that $XDG_DATA_DIRS falls back to the two default paths.
166   {
167     MockEnvironment env;
168     base::ScopedPathOverride home_override(base::DIR_HOME,
169                                            base::FilePath("/home/user"),
170                                            true /* absolute? */,
171                                            false /* create? */);
172     env.Set("XDG_DATA_HOME", "/user/path");
173     EXPECT_THAT(
174         FilePathsToStrings(GetDataSearchLocations(&env)),
175         ElementsAre("/user/path",
176                     "/usr/local/share",
177                     "/usr/share"));
178   }
179 }
180
181 TEST(ShellIntegrationTest, GetExistingShortcutContents) {
182   const char kTemplateFilename[] = "shortcut-test.desktop";
183   base::FilePath kTemplateFilepath(kTemplateFilename);
184   const char kTestData1[] = "a magical testing string";
185   const char kTestData2[] = "a different testing string";
186
187   content::BrowserTaskEnvironment task_environment;
188
189   // Test that it searches $XDG_DATA_HOME/applications.
190   {
191     base::ScopedTempDir temp_dir;
192     ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
193
194     MockEnvironment env;
195     env.Set("XDG_DATA_HOME", temp_dir.GetPath().value());
196     // Create a file in a non-applications directory. This should be ignored.
197     ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kTemplateFilename),
198                                 kTestData2));
199     ASSERT_TRUE(
200         base::CreateDirectory(temp_dir.GetPath().Append("applications")));
201     ASSERT_TRUE(base::WriteFile(
202         temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
203         kTestData1));
204     std::string contents;
205     ASSERT_TRUE(
206         GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
207     EXPECT_EQ(kTestData1, contents);
208   }
209
210   // Test that it falls back to $HOME/.local/share/applications.
211   {
212     base::ScopedTempDir temp_dir;
213     ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
214
215     MockEnvironment env;
216     base::ScopedPathOverride home_override(base::DIR_HOME, temp_dir.GetPath(),
217                                            true /* absolute? */,
218                                            false /* create? */);
219     ASSERT_TRUE(base::CreateDirectory(
220         temp_dir.GetPath().Append(".local/share/applications")));
221     ASSERT_TRUE(base::WriteFile(temp_dir.GetPath()
222                                     .Append(".local/share/applications")
223                                     .Append(kTemplateFilename),
224                                 kTestData1));
225     std::string contents;
226     ASSERT_TRUE(
227         GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
228     EXPECT_EQ(kTestData1, contents);
229   }
230
231   // Test that it searches $XDG_DATA_DIRS/applications.
232   {
233     base::ScopedTempDir temp_dir;
234     ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
235
236     MockEnvironment env;
237     env.Set("XDG_DATA_DIRS", temp_dir.GetPath().value());
238     ASSERT_TRUE(
239         base::CreateDirectory(temp_dir.GetPath().Append("applications")));
240     ASSERT_TRUE(base::WriteFile(
241         temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
242         kTestData2));
243     std::string contents;
244     ASSERT_TRUE(
245         GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
246     EXPECT_EQ(kTestData2, contents);
247   }
248
249   // Test that it searches $X/applications for each X in $XDG_DATA_DIRS.
250   {
251     base::ScopedTempDir temp_dir1;
252     ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
253     base::ScopedTempDir temp_dir2;
254     ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
255
256     MockEnvironment env;
257     env.Set("XDG_DATA_DIRS",
258             temp_dir1.GetPath().value() + ":" + temp_dir2.GetPath().value());
259     // Create a file in a non-applications directory. This should be ignored.
260     ASSERT_TRUE(base::WriteFile(temp_dir1.GetPath().Append(kTemplateFilename),
261                                 kTestData1));
262     // Only create a findable desktop file in the second path.
263     ASSERT_TRUE(
264         base::CreateDirectory(temp_dir2.GetPath().Append("applications")));
265     ASSERT_TRUE(base::WriteFile(
266         temp_dir2.GetPath().Append("applications").Append(kTemplateFilename),
267         kTestData2));
268     std::string contents;
269     ASSERT_TRUE(
270         GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
271     EXPECT_EQ(kTestData2, contents);
272   }
273 }
274
275 TEST(ShellIntegrationTest, GetExistingProfileShortcutFilenames) {
276   base::FilePath kProfilePath("a/b/c/Profile Name?");
277   const char kApp1Filename[] = "chrome-extension1-Profile_Name_.desktop";
278   const char kApp2Filename[] = "chrome-extension2-Profile_Name_.desktop";
279   const char kUnrelatedAppFilename[] = "chrome-extension-Other_Profile.desktop";
280
281   content::BrowserTaskEnvironment task_environment;
282
283   base::ScopedTempDir temp_dir;
284   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
285   ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp1Filename), ""));
286   ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp2Filename), ""));
287   // This file should not be returned in the results.
288   ASSERT_TRUE(
289       base::WriteFile(temp_dir.GetPath().Append(kUnrelatedAppFilename), ""));
290   std::vector<base::FilePath> paths =
291       GetExistingProfileShortcutFilenames(kProfilePath, temp_dir.GetPath());
292   // Path order is arbitrary. Sort the output for consistency.
293   std::sort(paths.begin(), paths.end());
294   EXPECT_THAT(paths,
295               ElementsAre(base::FilePath(kApp1Filename),
296                           base::FilePath(kApp2Filename)));
297 }
298
299 TEST(ShellIntegrationTest, GetWebShortcutFilename) {
300   const struct {
301     const char* const path;
302     const char* const url;
303   } test_cases[] = {
304     { "http___foo_.desktop", "http://foo" },
305     { "http___foo_bar_.desktop", "http://foo/bar/" },
306     { "http___foo_bar_a=b&c=d.desktop", "http://foo/bar?a=b&c=d" },
307
308     // Now we're starting to be more evil...
309     { "http___foo_.desktop", "http://foo/bar/baz/../../../../../" },
310     { "http___foo_.desktop", "http://foo/bar/././../baz/././../" },
311     { "http___.._.desktop", "http://../../../../" },
312   };
313   for (size_t i = 0; i < std::size(test_cases); i++) {
314     EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" +
315               test_cases[i].path,
316               GetWebShortcutFilename(GURL(test_cases[i].url)).value()) <<
317         " while testing " << test_cases[i].url;
318   }
319 }
320
321 TEST(ShellIntegrationTest, GetDesktopFileContents) {
322   const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
323   const struct {
324     const char* const url;
325     const char* const title;
326     const char* const icon_name;
327     const char* const categories;
328     const char* const mime_type;
329     bool nodisplay;
330     const char* const expected_output;
331   } test_cases[] = {
332       // Real-world case.
333       {"http://gmail.com", "GMail", "chrome-http__gmail.com", "", "", false,
334
335        "#!/usr/bin/env xdg-open\n"
336        "[Desktop Entry]\n"
337        "Version=1.0\n"
338        "Terminal=false\n"
339        "Type=Application\n"
340        "Name=GMail\n"
341        "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
342        "Icon=chrome-http__gmail.com\n"
343        "StartupWMClass=gmail.com\n"},
344
345       // Make sure that empty icons are replaced by the chrome icon.
346       {"http://gmail.com", "GMail", "", "", "", false,
347
348        "#!/usr/bin/env xdg-open\n"
349        "[Desktop Entry]\n"
350        "Version=1.0\n"
351        "Terminal=false\n"
352        "Type=Application\n"
353        "Name=GMail\n"
354        "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
355 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
356        "Icon=google-chrome\n"
357 #else
358        "Icon=chromium-browser\n"
359 #endif
360        "StartupWMClass=gmail.com\n"},
361
362       // Test adding categories and NoDisplay=true.
363       {"http://gmail.com", "GMail", "chrome-http__gmail.com",
364        "Graphics;Education;", "", true,
365
366        "#!/usr/bin/env xdg-open\n"
367        "[Desktop Entry]\n"
368        "Version=1.0\n"
369        "Terminal=false\n"
370        "Type=Application\n"
371        "Name=GMail\n"
372        "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
373        "Icon=chrome-http__gmail.com\n"
374        "Categories=Graphics;Education;\n"
375        "NoDisplay=true\n"
376        "StartupWMClass=gmail.com\n"},
377
378       // Now we're starting to be more evil...
379       {"http://evil.com/evil --join-the-b0tnet", "Ownz0red\nExec=rm -rf /",
380        "chrome-http__evil.com_evil", "", "", false,
381
382        "#!/usr/bin/env xdg-open\n"
383        "[Desktop Entry]\n"
384        "Version=1.0\n"
385        "Terminal=false\n"
386        "Type=Application\n"
387        "Name=http://evil.com/evil%20--join-the-b0tnet\n"
388        "Exec=/opt/google/chrome/google-chrome "
389        "--app=http://evil.com/evil%20--join-the-b0tnet\n"
390        "Icon=chrome-http__evil.com_evil\n"
391        "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"},
392       {"http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
393        "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
394
395        "#!/usr/bin/env xdg-open\n"
396        "[Desktop Entry]\n"
397        "Version=1.0\n"
398        "Terminal=false\n"
399        "Type=Application\n"
400        "Name=Innocent Title\n"
401        "Exec=/opt/google/chrome/google-chrome "
402        "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20"
403        // Note: $ is escaped as \$ within an arg to Exec, and then
404        // the \ is escaped as \\ as all strings in a Desktop file should
405        // be; finally, \\ becomes \\\\ when represented in a C++ string!
406        "-rf%20\\\\$HOME%20%3Eownz0red\"\n"
407        "Icon=chrome-http__evil.com_evil\n"
408        "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
409        "rm%20-rf%20$HOME%20%3Eownz0red\n"},
410       {"http://evil.com/evil | cat `echo ownz0red` >/dev/null",
411        "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
412
413        "#!/usr/bin/env xdg-open\n"
414        "[Desktop Entry]\n"
415        "Version=1.0\n"
416        "Terminal=false\n"
417        "Type=Application\n"
418        "Name=Innocent Title\n"
419        "Exec=/opt/google/chrome/google-chrome "
420        "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red"
421        "%60%20%3E/dev/null\n"
422        "Icon=chrome-http__evil.com_evil\n"
423        "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
424        "%60%20%3E_dev_null\n"},
425       // Test setting mime type
426       {"https://paint.app", "Paint", "chrome-https__paint.app", "Image",
427        "image/png;image/jpg", false,
428
429        "#!/usr/bin/env xdg-open\n"
430        "[Desktop Entry]\n"
431        "Version=1.0\n"
432        "Terminal=false\n"
433        "Type=Application\n"
434        "Name=Paint\n"
435        "MimeType=image/png;image/jpg\n"
436        "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/ %U\n"
437        "Icon=chrome-https__paint.app\n"
438        "Categories=Image\n"
439        "StartupWMClass=paint.app\n"},
440
441       // Test evil mime type.
442       {"https://paint.app", "Evil Paint", "chrome-https__paint.app", "Image",
443        "image/png\nExec=rm -rf /", false,
444
445        "#!/usr/bin/env xdg-open\n"
446        "[Desktop Entry]\n"
447        "Version=1.0\n"
448        "Terminal=false\n"
449        "Type=Application\n"
450        "Name=Evil Paint\n"
451        "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/\n"
452        "Icon=chrome-https__paint.app\n"
453        "Categories=Image\n"
454        "StartupWMClass=paint.app\n"}};
455
456   for (size_t i = 0; i < std::size(test_cases); i++) {
457     SCOPED_TRACE(i);
458     EXPECT_EQ(
459         test_cases[i].expected_output,
460         GetDesktopFileContents(
461             kChromeExePath,
462             web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
463             GURL(test_cases[i].url), std::string(),
464             base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
465             base::FilePath(), test_cases[i].categories, test_cases[i].mime_type,
466             test_cases[i].nodisplay, "", {}));
467   }
468 }
469
470 TEST(ShellIntegrationTest, GetDesktopFileContentsForApps) {
471   const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
472   const struct {
473     const char* const url;
474     const char* const title;
475     const char* const icon_name;
476     bool nodisplay;
477     std::set<web_app::DesktopActionInfo> action_info;
478     const char* const expected_output;
479   } test_cases[] = {
480       // Test Shortcut Menu actions.
481       {"https://example.app",
482        "Lawful example",
483        "IconName",
484        false,
485        {
486            web_app::DesktopActionInfo("action1", "Action 1",
487                                       GURL("https://example.com/action1")),
488            web_app::DesktopActionInfo("action2", "Action 2",
489                                       GURL("https://example.com/action2")),
490            web_app::DesktopActionInfo("action3", "Action 3",
491                                       GURL("https://example.com/action3")),
492            web_app::DesktopActionInfo("action4", "Action 4",
493                                       GURL("https://example.com/action4")),
494            web_app::DesktopActionInfo("action5", "Action 5",
495                                       GURL("https://example.com/action%205")),
496        },
497
498        "#!/usr/bin/env xdg-open\n"
499        "[Desktop Entry]\n"
500        "Version=1.0\n"
501        "Terminal=false\n"
502        "Type=Application\n"
503        "Name=Lawful example\n"
504        "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId\n"
505        "Icon=IconName\n"
506        "StartupWMClass=example.app\n"
507        "Actions=action1;action2;action3;action4;action5\n\n"
508        "[Desktop Action action1]\n"
509        "Name=Action 1\n"
510        "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
511        "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
512        "action1\n\n"
513        "[Desktop Action action2]\n"
514        "Name=Action 2\n"
515        "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
516        "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
517        "action2\n\n"
518        "[Desktop Action action3]\n"
519        "Name=Action 3\n"
520        "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
521        "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
522        "action3\n\n"
523        "[Desktop Action action4]\n"
524        "Name=Action 4\n"
525        "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
526        "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
527        "action4\n\n"
528        "[Desktop Action action5]\n"
529        "Name=Action 5\n"
530        "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
531        "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
532        "action%%205\n"},
533   };
534
535   for (size_t i = 0; i < std::size(test_cases); i++) {
536     SCOPED_TRACE(i);
537     EXPECT_EQ(
538         test_cases[i].expected_output,
539         GetDesktopFileContents(
540             kChromeExePath,
541             web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
542             GURL(test_cases[i].url), "TestAppId",
543             base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
544             base::FilePath(), "", "", test_cases[i].nodisplay, "",
545             test_cases[i].action_info));
546   }
547 }
548
549 TEST(ShellIntegrationTest, GetDirectoryFileContents) {
550   const struct {
551     const char* const title;
552     const char* const icon_name;
553     const char* const expected_output;
554   } test_cases[] = {
555       // Real-world case.
556       {"Chrome Apps", "chrome-apps",
557
558        "[Desktop Entry]\n"
559        "Version=1.0\n"
560        "Type=Directory\n"
561        "Name=Chrome Apps\n"
562        "Icon=chrome-apps\n"},
563
564       // Make sure that empty icons are replaced by the chrome icon.
565       {"Chrome Apps", "",
566
567        "[Desktop Entry]\n"
568        "Version=1.0\n"
569        "Type=Directory\n"
570        "Name=Chrome Apps\n"
571 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
572        "Icon=google-chrome\n"
573 #else
574        "Icon=chromium-browser\n"
575 #endif
576       },
577   };
578
579   for (size_t i = 0; i < std::size(test_cases); i++) {
580     SCOPED_TRACE(i);
581     EXPECT_EQ(test_cases[i].expected_output,
582               GetDirectoryFileContents(base::ASCIIToUTF16(test_cases[i].title),
583                                        test_cases[i].icon_name));
584   }
585 }
586
587 TEST(ShellIntegrationTest, GetMimeTypesRegistrationFilename) {
588   const struct {
589     const char* const profile_path;
590     const char* const app_id;
591     const char* const expected_filename;
592   } test_cases[] = {
593       {"Default", "app-id", "-app-id-Default.xml"},
594       {"Default Profile", "app-id", "-app-id-Default_Profile.xml"},
595       {"foo/Default", "app-id", "-app-id-Default.xml"},
596       {"Default*Profile", "app-id", "-app-id-Default_Profile.xml"}};
597   std::string browser_name(chrome::kBrowserProcessExecutableName);
598
599   for (const auto& test_case : test_cases) {
600     const base::FilePath filename =
601         GetMimeTypesRegistrationFilename(base::FilePath(test_case.profile_path),
602                                          webapps::AppId(test_case.app_id));
603     EXPECT_EQ(browser_name + test_case.expected_filename, filename.value());
604   }
605 }
606
607 TEST(ShellIntegrationTest, GetMimeTypesRegistrationFileContents) {
608   apps::FileHandlers file_handlers;
609   {
610     apps::FileHandler file_handler;
611     {
612       apps::FileHandler::AcceptEntry accept_entry;
613       accept_entry.mime_type = "application/foo";
614       accept_entry.file_extensions.insert(".foo");
615       file_handler.accept.push_back(accept_entry);
616     }
617     file_handler.display_name = u"FoO";
618     file_handlers.push_back(file_handler);
619   }
620   {
621     apps::FileHandler file_handler;
622     {
623       apps::FileHandler::AcceptEntry accept_entry;
624       accept_entry.mime_type = "application/foobar";
625       accept_entry.file_extensions.insert(".foobar");
626       file_handler.accept.push_back(accept_entry);
627     }
628     file_handlers.push_back(file_handler);
629   }
630   {
631     apps::FileHandler file_handler;
632     {
633       apps::FileHandler::AcceptEntry accept_entry;
634       accept_entry.mime_type = "application/bar";
635       // A name that has a reserved XML character.
636       file_handler.display_name = u"ba<r";
637       accept_entry.file_extensions.insert(".bar");
638       accept_entry.file_extensions.insert(".baz");
639       file_handler.accept.push_back(accept_entry);
640     }
641     file_handlers.push_back(file_handler);
642   }
643
644   const std::string file_contents =
645       GetMimeTypesRegistrationFileContents(file_handlers);
646   const std::string expected_file_contents =
647       "<?xml version=\"1.0\"?>\n"
648       "<mime-info "
649       "xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
650       " <mime-type type=\"application/foo\">\n"
651       "  <comment>FoO</comment>\n"
652       "  <glob pattern=\"*.foo\"/>\n"
653       " </mime-type>\n"
654       " <mime-type type=\"application/foobar\">\n"
655       "  <glob pattern=\"*.foobar\"/>\n"
656       " </mime-type>\n"
657       " <mime-type type=\"application/bar\">\n"
658       "  <comment>ba&lt;r</comment>\n"
659       "  <glob pattern=\"*.bar\"/>\n"
660       "  <glob pattern=\"*.baz\"/>\n"
661       " </mime-type>\n"
662       "</mime-info>\n";
663
664   EXPECT_EQ(file_contents, expected_file_contents);
665 }
666
667 // The WM class name may be either capitalised or not, depending on the
668 // platform.
669 void CheckProgramClassClass(const std::string& class_name) {
670   if (ui::OzonePlatform::GetPlatformNameForTest() == "x11") {
671     EXPECT_EQ("Foo", class_name);
672   } else {
673     EXPECT_EQ("foo", class_name);
674   }
675 }
676
677 TEST(ShellIntegrationTest, WmClass) {
678   base::CommandLine command_line((base::FilePath()));
679   EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
680   CheckProgramClassClass(
681       internal::GetProgramClassClass(command_line, "foo.desktop"));
682
683   command_line.AppendSwitchASCII("class", "baR");
684   EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
685   EXPECT_EQ("baR", internal::GetProgramClassClass(command_line, "foo.desktop"));
686
687   command_line = base::CommandLine(base::FilePath());
688   command_line.AppendSwitchASCII("user-data-dir", "/tmp/baz");
689   EXPECT_EQ("foo (/tmp/baz)",
690             internal::GetProgramClassName(command_line, "foo.desktop"));
691   CheckProgramClassClass(
692       internal::GetProgramClassClass(command_line, "foo.desktop"));
693 }
694
695 TEST(ShellIntegrationTest, GetDesktopEntryStringValueFromFromDesktopFile) {
696   const char* const kDesktopFileContents =
697       "#!/usr/bin/env xdg-open\n"
698       "[Desktop Entry]\n"
699       "Version=1.0\n"
700       "Terminal=false\n"
701       "Type=Application\n"
702       "Name=Lawful example\n"
703       "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId\n"
704       "Icon=IconName\n"
705       "StartupWMClass=example.app\n"
706       "Actions=action1\n\n"
707       "[Desktop Action action1]\n"
708       "Name=Action 1\n"
709       "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId --Test"
710       "Action1=Value";
711
712   // Verify basic strings return the right value.
713   EXPECT_EQ("Lawful example",
714             shell_integration_linux::internal::
715                 GetDesktopEntryStringValueFromFromDesktopFileForTest(
716                     "Name", kDesktopFileContents));
717   EXPECT_EQ("example.app",
718             shell_integration_linux::internal::
719                 GetDesktopEntryStringValueFromFromDesktopFileForTest(
720                     "StartupWMClass", kDesktopFileContents));
721   // Verify that booleans are returned correctly.
722   EXPECT_EQ("false", shell_integration_linux::internal::
723                          GetDesktopEntryStringValueFromFromDesktopFileForTest(
724                              "Terminal", kDesktopFileContents));
725   // Verify that numbers are returned correctly.
726   EXPECT_EQ("1.0", shell_integration_linux::internal::
727                        GetDesktopEntryStringValueFromFromDesktopFileForTest(
728                            "Version", kDesktopFileContents));
729   // Verify that a non-existent key returns an empty string.
730   EXPECT_EQ("", shell_integration_linux::internal::
731                     GetDesktopEntryStringValueFromFromDesktopFileForTest(
732                         "DoesNotExistKey", kDesktopFileContents));
733   // Verify that a non-existent key in [Desktop Entry] section returns an empty
734   // string.
735   EXPECT_EQ("", shell_integration_linux::internal::
736                     GetDesktopEntryStringValueFromFromDesktopFileForTest(
737                         "Action1", kDesktopFileContents));
738 }
739
740 }  // namespace shell_integration_linux