blob: 43d4a6a7cdd5b985a477ed084578d970ceb1e150 [file] [log] [blame]
[email protected]e7f79872013-09-13 01:39:311// Copyright 2013 The Chromium Authors. All rights reserved.
[email protected]b96aa932009-08-12 21:34:492// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]e7f79872013-09-13 01:39:315#include "chrome/browser/shell_integration_linux.h"
[email protected]b96aa932009-08-12 21:34:496
avi664c07b2015-12-26 02:18:317#include <stddef.h>
8
[email protected]111f0282013-08-05 10:09:299#include <algorithm>
[email protected]07753f0f2013-02-05 07:52:5910#include <cstdlib>
[email protected]af71d642010-03-12 10:29:0411#include <map>
thestigd0b662342015-01-16 08:21:1912#include <vector>
[email protected]af71d642010-03-12 10:29:0413
[email protected]14fbaed2013-05-02 07:54:0214#include "base/base_paths.h"
[email protected]2164e512014-01-22 09:32:1015#include "base/command_line.h"
Jan Wilken Dörriebf97d442020-12-09 19:01:5816#include "base/containers/contains.h"
[email protected]e7f79872013-09-13 01:39:3117#include "base/environment.h"
[email protected]57999812013-02-24 05:40:5218#include "base/files/file_path.h"
thestig18dfb7a52014-08-26 10:44:0419#include "base/files/file_util.h"
[email protected]ea1a3f62012-11-16 20:34:2320#include "base/files/scoped_temp_dir.h"
Peter Kasting9eb10b02021-04-29 03:10:4821#include "base/strings/string_piece.h"
[email protected]24a555b62013-06-10 22:01:1722#include "base/strings/string_util.h"
[email protected]e309f312013-06-07 21:50:0823#include "base/strings/utf_string_conversions.h"
[email protected]14fbaed2013-05-02 07:54:0224#include "base/test/scoped_path_override.h"
Nico Webereaa08412019-08-14 01:24:3725#include "build/branding_buildflags.h"
Song Fangzhen49468b22021-09-02 15:41:5926#include "chrome/browser/web_applications/web_app_helpers.h"
Song Fangzhencda4af62021-09-09 05:24:0227#include "chrome/browser/web_applications/web_app_id.h"
28#include "chrome/browser/web_applications/web_app_shortcut.h"
[email protected]42896802009-08-28 23:39:4429#include "chrome/common/chrome_constants.h"
Robert Woods954a2182020-03-20 06:08:2730#include "components/services/app_service/public/cpp/file_handler.h"
Gabriel Charettec7108742019-08-23 03:31:4031#include "content/public/test/browser_task_environment.h"
[email protected]111f0282013-08-05 10:09:2932#include "testing/gmock/include/gmock/gmock.h"
[email protected]b96aa932009-08-12 21:34:4933#include "testing/gtest/include/gtest/gtest.h"
[email protected]761fa4702013-07-02 15:25:1534#include "url/gurl.h"
[email protected]b96aa932009-08-12 21:34:4935
Alexander Dunaev66b50ba2021-08-10 04:11:4636#if defined(USE_OZONE)
37#include "ui/base/ui_base_features.h"
38#include "ui/ozone/public/ozone_platform.h"
39#endif
40
[email protected]111f0282013-08-05 10:09:2941using ::testing::ElementsAre;
[email protected]631bb742011-11-02 11:29:3942
[email protected]06bfeb12014-05-27 14:00:0943namespace shell_integration_linux {
44
[email protected]af71d642010-03-12 10:29:0445namespace {
46
47// Provides mock environment variables values based on a stored map.
[email protected]76b90d312010-08-03 03:00:5048class MockEnvironment : public base::Environment {
[email protected]af71d642010-03-12 10:29:0449 public:
[email protected]76b90d312010-08-03 03:00:5050 MockEnvironment() {}
[email protected]af71d642010-03-12 10:29:0451
thestig0c412e852016-06-30 08:04:4052 void Set(base::StringPiece name, const std::string& value) {
Peter Kasting9eb10b02021-04-29 03:10:4853 variables_[std::string(name)] = value;
[email protected]af71d642010-03-12 10:29:0454 }
55
thestig0c412e852016-06-30 08:04:4056 bool GetVar(base::StringPiece variable_name, std::string* result) override {
Peter Kasting9eb10b02021-04-29 03:10:4857 if (base::Contains(variables_, std::string(variable_name))) {
58 *result = variables_[std::string(variable_name)];
[email protected]af71d642010-03-12 10:29:0459 return true;
60 }
61
62 return false;
63 }
64
thestig0c412e852016-06-30 08:04:4065 bool SetVar(base::StringPiece variable_name,
dchenge1bc7982014-10-30 00:32:4066 const std::string& new_value) override {
[email protected]fc586c72010-07-31 16:55:4067 ADD_FAILURE();
68 return false;
69 }
70
thestig0c412e852016-06-30 08:04:4071 bool UnSetVar(base::StringPiece variable_name) override {
[email protected]fc586c72010-07-31 16:55:4072 ADD_FAILURE();
[email protected]e9032c62010-07-16 03:34:2573 return false;
[email protected]ac7264c2010-07-08 13:32:5174 }
75
[email protected]af71d642010-03-12 10:29:0476 private:
77 std::map<std::string, std::string> variables_;
78
[email protected]76b90d312010-08-03 03:00:5079 DISALLOW_COPY_AND_ASSIGN(MockEnvironment);
[email protected]af71d642010-03-12 10:29:0480};
81
thestigd0b662342015-01-16 08:21:1982// This helps EXPECT_THAT(..., ElementsAre(...)) print out more meaningful
83// failure messages.
84std::vector<std::string> FilePathsToStrings(
85 const std::vector<base::FilePath>& paths) {
86 std::vector<std::string> values;
87 for (const auto& path : paths)
88 values.push_back(path.value());
89 return values;
90}
91
[email protected]af71d642010-03-12 10:29:0492} // namespace
93
[email protected]111f0282013-08-05 10:09:2994TEST(ShellIntegrationTest, GetDataWriteLocation) {
Gabriel Charette798fde72019-08-20 22:24:0495 content::BrowserTaskEnvironment task_environment;
[email protected]111f0282013-08-05 10:09:2996
97 // Test that it returns $XDG_DATA_HOME.
98 {
99 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49100 base::ScopedPathOverride home_override(base::DIR_HOME,
101 base::FilePath("/home/user"),
102 true /* absolute? */,
103 false /* create? */);
[email protected]111f0282013-08-05 10:09:29104 env.Set("XDG_DATA_HOME", "/user/path");
thestigd2b1fcf2015-01-21 22:11:49105 base::FilePath path = GetDataWriteLocation(&env);
thestigd0b662342015-01-16 08:21:19106 EXPECT_EQ("/user/path", path.value());
[email protected]111f0282013-08-05 10:09:29107 }
108
109 // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
110 {
111 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49112 base::ScopedPathOverride home_override(base::DIR_HOME,
113 base::FilePath("/home/user"),
114 true /* absolute? */,
115 false /* create? */);
116 base::FilePath path = GetDataWriteLocation(&env);
thestigd0b662342015-01-16 08:21:19117 EXPECT_EQ("/home/user/.local/share", path.value());
[email protected]111f0282013-08-05 10:09:29118 }
[email protected]111f0282013-08-05 10:09:29119}
120
121TEST(ShellIntegrationTest, GetDataSearchLocations) {
Gabriel Charette798fde72019-08-20 22:24:04122 content::BrowserTaskEnvironment task_environment;
[email protected]111f0282013-08-05 10:09:29123
124 // Test that it returns $XDG_DATA_HOME + $XDG_DATA_DIRS.
125 {
126 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49127 base::ScopedPathOverride home_override(base::DIR_HOME,
128 base::FilePath("/home/user"),
129 true /* absolute? */,
130 false /* create? */);
[email protected]111f0282013-08-05 10:09:29131 env.Set("XDG_DATA_HOME", "/user/path");
132 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
133 EXPECT_THAT(
thestigd0b662342015-01-16 08:21:19134 FilePathsToStrings(GetDataSearchLocations(&env)),
135 ElementsAre("/user/path",
136 "/system/path/1",
137 "/system/path/2"));
[email protected]111f0282013-08-05 10:09:29138 }
139
140 // Test that $XDG_DATA_HOME falls back to $HOME/.local/share.
141 {
142 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49143 base::ScopedPathOverride home_override(base::DIR_HOME,
144 base::FilePath("/home/user"),
145 true /* absolute? */,
146 false /* create? */);
[email protected]111f0282013-08-05 10:09:29147 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
148 EXPECT_THAT(
thestigd0b662342015-01-16 08:21:19149 FilePathsToStrings(GetDataSearchLocations(&env)),
150 ElementsAre("/home/user/.local/share",
151 "/system/path/1",
152 "/system/path/2"));
[email protected]111f0282013-08-05 10:09:29153 }
154
155 // Test that if neither $XDG_DATA_HOME nor $HOME are specified, it still
156 // succeeds.
157 {
158 MockEnvironment env;
159 env.Set("XDG_DATA_DIRS", "/system/path/1:/system/path/2");
thestigd2b1fcf2015-01-21 22:11:49160 std::vector<std::string> results =
161 FilePathsToStrings(GetDataSearchLocations(&env));
162 ASSERT_EQ(3U, results.size());
163 EXPECT_FALSE(results[0].empty());
164 EXPECT_EQ("/system/path/1", results[1]);
165 EXPECT_EQ("/system/path/2", results[2]);
[email protected]111f0282013-08-05 10:09:29166 }
167
168 // Test that $XDG_DATA_DIRS falls back to the two default paths.
169 {
170 MockEnvironment env;
thestigd2b1fcf2015-01-21 22:11:49171 base::ScopedPathOverride home_override(base::DIR_HOME,
172 base::FilePath("/home/user"),
173 true /* absolute? */,
174 false /* create? */);
[email protected]111f0282013-08-05 10:09:29175 env.Set("XDG_DATA_HOME", "/user/path");
176 EXPECT_THAT(
thestigd0b662342015-01-16 08:21:19177 FilePathsToStrings(GetDataSearchLocations(&env)),
178 ElementsAre("/user/path",
179 "/usr/local/share",
180 "/usr/share"));
[email protected]111f0282013-08-05 10:09:29181 }
182}
183
[email protected]d81a63c02013-03-07 08:49:04184TEST(ShellIntegrationTest, GetExistingShortcutContents) {
185 const char kTemplateFilename[] = "shortcut-test.desktop";
186 base::FilePath kTemplateFilepath(kTemplateFilename);
[email protected]af71d642010-03-12 10:29:04187 const char kTestData1[] = "a magical testing string";
188 const char kTestData2[] = "a different testing string";
189
Gabriel Charette798fde72019-08-20 22:24:04190 content::BrowserTaskEnvironment task_environment;
[email protected]af71d642010-03-12 10:29:04191
[email protected]58bf9252013-03-06 04:12:36192 // Test that it searches $XDG_DATA_HOME/applications.
[email protected]af71d642010-03-12 10:29:04193 {
[email protected]ea1a3f62012-11-16 20:34:23194 base::ScopedTempDir temp_dir;
[email protected]af71d642010-03-12 10:29:04195 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
196
[email protected]76b90d312010-08-03 03:00:50197 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58198 env.Set("XDG_DATA_HOME", temp_dir.GetPath().value());
[email protected]58bf9252013-03-06 04:12:36199 // Create a file in a non-applications directory. This should be ignored.
Lei Zhangeebbef62020-05-05 20:16:05200 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kTemplateFilename),
201 kTestData2));
vabr8c498ea42016-09-15 12:41:58202 ASSERT_TRUE(
203 base::CreateDirectory(temp_dir.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05204 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58205 temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19206 kTestData1));
[email protected]af71d642010-03-12 10:29:04207 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04208 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09209 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]af71d642010-03-12 10:29:04210 EXPECT_EQ(kTestData1, contents);
211 }
212
[email protected]58bf9252013-03-06 04:12:36213 // Test that it falls back to $HOME/.local/share/applications.
214 {
215 base::ScopedTempDir temp_dir;
216 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
217
218 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58219 base::ScopedPathOverride home_override(base::DIR_HOME, temp_dir.GetPath(),
thestigd2b1fcf2015-01-21 22:11:49220 true /* absolute? */,
221 false /* create? */);
[email protected]426d1c92013-12-03 20:08:54222 ASSERT_TRUE(base::CreateDirectory(
vabr8c498ea42016-09-15 12:41:58223 temp_dir.GetPath().Append(".local/share/applications")));
Lei Zhangeebbef62020-05-05 20:16:05224 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath()
225 .Append(".local/share/applications")
226 .Append(kTemplateFilename),
227 kTestData1));
[email protected]58bf9252013-03-06 04:12:36228 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04229 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09230 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]58bf9252013-03-06 04:12:36231 EXPECT_EQ(kTestData1, contents);
232 }
233
234 // Test that it searches $XDG_DATA_DIRS/applications.
[email protected]af71d642010-03-12 10:29:04235 {
[email protected]ea1a3f62012-11-16 20:34:23236 base::ScopedTempDir temp_dir;
[email protected]af71d642010-03-12 10:29:04237 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
238
[email protected]76b90d312010-08-03 03:00:50239 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58240 env.Set("XDG_DATA_DIRS", temp_dir.GetPath().value());
241 ASSERT_TRUE(
242 base::CreateDirectory(temp_dir.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05243 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58244 temp_dir.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19245 kTestData2));
[email protected]af71d642010-03-12 10:29:04246 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04247 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09248 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]af71d642010-03-12 10:29:04249 EXPECT_EQ(kTestData2, contents);
250 }
251
[email protected]58bf9252013-03-06 04:12:36252 // Test that it searches $X/applications for each X in $XDG_DATA_DIRS.
[email protected]af71d642010-03-12 10:29:04253 {
[email protected]58bf9252013-03-06 04:12:36254 base::ScopedTempDir temp_dir1;
255 ASSERT_TRUE(temp_dir1.CreateUniqueTempDir());
256 base::ScopedTempDir temp_dir2;
257 ASSERT_TRUE(temp_dir2.CreateUniqueTempDir());
[email protected]af71d642010-03-12 10:29:04258
[email protected]76b90d312010-08-03 03:00:50259 MockEnvironment env;
vabr8c498ea42016-09-15 12:41:58260 env.Set("XDG_DATA_DIRS",
261 temp_dir1.GetPath().value() + ":" + temp_dir2.GetPath().value());
[email protected]58bf9252013-03-06 04:12:36262 // Create a file in a non-applications directory. This should be ignored.
Lei Zhangeebbef62020-05-05 20:16:05263 ASSERT_TRUE(base::WriteFile(temp_dir1.GetPath().Append(kTemplateFilename),
264 kTestData1));
[email protected]58bf9252013-03-06 04:12:36265 // Only create a findable desktop file in the second path.
vabr8c498ea42016-09-15 12:41:58266 ASSERT_TRUE(
267 base::CreateDirectory(temp_dir2.GetPath().Append("applications")));
Lei Zhangeebbef62020-05-05 20:16:05268 ASSERT_TRUE(base::WriteFile(
vabr8c498ea42016-09-15 12:41:58269 temp_dir2.GetPath().Append("applications").Append(kTemplateFilename),
thestigd0b662342015-01-16 08:21:19270 kTestData2));
[email protected]af71d642010-03-12 10:29:04271 std::string contents;
[email protected]d81a63c02013-03-07 08:49:04272 ASSERT_TRUE(
[email protected]06bfeb12014-05-27 14:00:09273 GetExistingShortcutContents(&env, kTemplateFilepath, &contents));
[email protected]d81a63c02013-03-07 08:49:04274 EXPECT_EQ(kTestData2, contents);
275 }
276}
277
[email protected]111f0282013-08-05 10:09:29278TEST(ShellIntegrationTest, GetExistingProfileShortcutFilenames) {
279 base::FilePath kProfilePath("a/b/c/Profile Name?");
280 const char kApp1Filename[] = "chrome-extension1-Profile_Name_.desktop";
281 const char kApp2Filename[] = "chrome-extension2-Profile_Name_.desktop";
282 const char kUnrelatedAppFilename[] = "chrome-extension-Other_Profile.desktop";
283
Gabriel Charette798fde72019-08-20 22:24:04284 content::BrowserTaskEnvironment task_environment;
[email protected]111f0282013-08-05 10:09:29285
286 base::ScopedTempDir temp_dir;
287 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
Lei Zhangeebbef62020-05-05 20:16:05288 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp1Filename), ""));
289 ASSERT_TRUE(base::WriteFile(temp_dir.GetPath().Append(kApp2Filename), ""));
[email protected]111f0282013-08-05 10:09:29290 // This file should not be returned in the results.
Lei Zhangeebbef62020-05-05 20:16:05291 ASSERT_TRUE(
292 base::WriteFile(temp_dir.GetPath().Append(kUnrelatedAppFilename), ""));
[email protected]111f0282013-08-05 10:09:29293 std::vector<base::FilePath> paths =
vabr8c498ea42016-09-15 12:41:58294 GetExistingProfileShortcutFilenames(kProfilePath, temp_dir.GetPath());
[email protected]111f0282013-08-05 10:09:29295 // Path order is arbitrary. Sort the output for consistency.
296 std::sort(paths.begin(), paths.end());
297 EXPECT_THAT(paths,
298 ElementsAre(base::FilePath(kApp1Filename),
299 base::FilePath(kApp2Filename)));
300}
301
[email protected]0b7df36d2012-07-11 09:50:47302TEST(ShellIntegrationTest, GetWebShortcutFilename) {
[email protected]b96aa932009-08-12 21:34:49303 const struct {
thestigd0b662342015-01-16 08:21:19304 const char* const path;
305 const char* const url;
[email protected]b96aa932009-08-12 21:34:49306 } test_cases[] = {
thestigd0b662342015-01-16 08:21:19307 { "http___foo_.desktop", "http://foo" },
308 { "http___foo_bar_.desktop", "http://foo/bar/" },
309 { "http___foo_bar_a=b&c=d.desktop", "http://foo/bar?a=b&c=d" },
[email protected]b96aa932009-08-12 21:34:49310
311 // Now we're starting to be more evil...
thestigd0b662342015-01-16 08:21:19312 { "http___foo_.desktop", "http://foo/bar/baz/../../../../../" },
313 { "http___foo_.desktop", "http://foo/bar/././../baz/././../" },
314 { "http___.._.desktop", "http://../../../../" },
[email protected]b96aa932009-08-12 21:34:49315 };
Avi Drissman22f82872018-12-25 23:09:07316 for (size_t i = 0; i < base::size(test_cases); i++) {
[email protected]4f260d02010-12-23 18:35:42317 EXPECT_EQ(std::string(chrome::kBrowserProcessExecutableName) + "-" +
[email protected]42896802009-08-28 23:39:44318 test_cases[i].path,
[email protected]06bfeb12014-05-27 14:00:09319 GetWebShortcutFilename(GURL(test_cases[i].url)).value()) <<
[email protected]42896802009-08-28 23:39:44320 " while testing " << test_cases[i].url;
[email protected]b96aa932009-08-12 21:34:49321 }
322}
323
[email protected]a2778d32011-12-01 07:49:34324TEST(ShellIntegrationTest, GetDesktopFileContents) {
[email protected]14fbaed2013-05-02 07:54:02325 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
[email protected]b96aa932009-08-12 21:34:49326 const struct {
thestigd0b662342015-01-16 08:21:19327 const char* const url;
328 const char* const title;
329 const char* const icon_name;
330 const char* const categories;
Jay Harris05b50bd2019-10-30 01:48:38331 const char* const mime_type;
[email protected]d81a63c02013-03-07 08:49:04332 bool nodisplay;
thestigd0b662342015-01-16 08:21:19333 const char* const expected_output;
[email protected]b96aa932009-08-12 21:34:49334 } test_cases[] = {
Nico Webereaa08412019-08-14 01:24:37335 // Real-world case.
Jay Harris05b50bd2019-10-30 01:48:38336 {"http://gmail.com", "GMail", "chrome-http__gmail.com", "", "", false,
[email protected]b96aa932009-08-12 21:34:49337
Nico Webereaa08412019-08-14 01:24:37338 "#!/usr/bin/env xdg-open\n"
339 "[Desktop Entry]\n"
340 "Version=1.0\n"
341 "Terminal=false\n"
342 "Type=Application\n"
343 "Name=GMail\n"
344 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
345 "Icon=chrome-http__gmail.com\n"
346 "StartupWMClass=gmail.com\n"},
[email protected]82810fe12009-09-25 16:21:57347
Nico Webereaa08412019-08-14 01:24:37348 // Make sure that empty icons are replaced by the chrome icon.
Jay Harris05b50bd2019-10-30 01:48:38349 {"http://gmail.com", "GMail", "", "", "", false,
[email protected]0b303cc2009-09-28 22:35:15350
Nico Webereaa08412019-08-14 01:24:37351 "#!/usr/bin/env xdg-open\n"
352 "[Desktop Entry]\n"
353 "Version=1.0\n"
354 "Terminal=false\n"
355 "Type=Application\n"
356 "Name=GMail\n"
357 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
358#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
359 "Icon=google-chrome\n"
[email protected]a7e18f42013-12-19 02:10:37360#else
Nico Webereaa08412019-08-14 01:24:37361 "Icon=chromium-browser\n"
[email protected]a7e18f42013-12-19 02:10:37362#endif
Nico Webereaa08412019-08-14 01:24:37363 "StartupWMClass=gmail.com\n"},
[email protected]0b303cc2009-09-28 22:35:15364
Nico Webereaa08412019-08-14 01:24:37365 // Test adding categories and NoDisplay=true.
366 {"http://gmail.com", "GMail", "chrome-http__gmail.com",
Jay Harris05b50bd2019-10-30 01:48:38367 "Graphics;Education;", "", true,
[email protected]d81a63c02013-03-07 08:49:04368
Nico Webereaa08412019-08-14 01:24:37369 "#!/usr/bin/env xdg-open\n"
370 "[Desktop Entry]\n"
371 "Version=1.0\n"
372 "Terminal=false\n"
373 "Type=Application\n"
374 "Name=GMail\n"
375 "Exec=/opt/google/chrome/google-chrome --app=http://gmail.com/\n"
376 "Icon=chrome-http__gmail.com\n"
377 "Categories=Graphics;Education;\n"
378 "NoDisplay=true\n"
379 "StartupWMClass=gmail.com\n"},
[email protected]d81a63c02013-03-07 08:49:04380
Nico Webereaa08412019-08-14 01:24:37381 // Now we're starting to be more evil...
382 {"http://evil.com/evil --join-the-b0tnet", "Ownz0red\nExec=rm -rf /",
Jay Harris05b50bd2019-10-30 01:48:38383 "chrome-http__evil.com_evil", "", "", false,
[email protected]b96aa932009-08-12 21:34:49384
Nico Webereaa08412019-08-14 01:24:37385 "#!/usr/bin/env xdg-open\n"
386 "[Desktop Entry]\n"
387 "Version=1.0\n"
388 "Terminal=false\n"
389 "Type=Application\n"
390 "Name=http://evil.com/evil%20--join-the-b0tnet\n"
391 "Exec=/opt/google/chrome/google-chrome "
392 "--app=http://evil.com/evil%20--join-the-b0tnet\n"
393 "Icon=chrome-http__evil.com_evil\n"
394 "StartupWMClass=evil.com__evil%20--join-the-b0tnet\n"},
395 {"http://evil.com/evil; rm -rf /; \"; rm -rf $HOME >ownz0red",
Jay Harris05b50bd2019-10-30 01:48:38396 "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
[email protected]82810fe12009-09-25 16:21:57397
Nico Webereaa08412019-08-14 01:24:37398 "#!/usr/bin/env xdg-open\n"
399 "[Desktop Entry]\n"
400 "Version=1.0\n"
401 "Terminal=false\n"
402 "Type=Application\n"
403 "Name=Innocent Title\n"
404 "Exec=/opt/google/chrome/google-chrome "
405 "\"--app=http://evil.com/evil;%20rm%20-rf%20/;%20%22;%20rm%20"
406 // Note: $ is escaped as \$ within an arg to Exec, and then
407 // the \ is escaped as \\ as all strings in a Desktop file should
408 // be; finally, \\ becomes \\\\ when represented in a C++ string!
409 "-rf%20\\\\$HOME%20%3Eownz0red\"\n"
410 "Icon=chrome-http__evil.com_evil\n"
411 "StartupWMClass=evil.com__evil;%20rm%20-rf%20_;%20%22;%20"
412 "rm%20-rf%20$HOME%20%3Eownz0red\n"},
413 {"http://evil.com/evil | cat `echo ownz0red` >/dev/null",
Jay Harris05b50bd2019-10-30 01:48:38414 "Innocent Title", "chrome-http__evil.com_evil", "", "", false,
[email protected]82810fe12009-09-25 16:21:57415
Nico Webereaa08412019-08-14 01:24:37416 "#!/usr/bin/env xdg-open\n"
417 "[Desktop Entry]\n"
418 "Version=1.0\n"
419 "Terminal=false\n"
420 "Type=Application\n"
421 "Name=Innocent Title\n"
422 "Exec=/opt/google/chrome/google-chrome "
423 "--app=http://evil.com/evil%20%7C%20cat%20%60echo%20ownz0red"
424 "%60%20%3E/dev/null\n"
425 "Icon=chrome-http__evil.com_evil\n"
426 "StartupWMClass=evil.com__evil%20%7C%20cat%20%60echo%20ownz0red"
427 "%60%20%3E_dev_null\n"},
Jay Harris05b50bd2019-10-30 01:48:38428 // Test setting mime type
429 {"https://paint.app", "Paint", "chrome-https__paint.app", "Image",
430 "image/png;image/jpg", false,
431
432 "#!/usr/bin/env xdg-open\n"
433 "[Desktop Entry]\n"
434 "Version=1.0\n"
435 "Terminal=false\n"
436 "Type=Application\n"
437 "Name=Paint\n"
Jay Harrisf730b6e2019-11-25 23:49:06438 "MimeType=image/png;image/jpg\n"
Mike Jacksoncfb1bc12021-03-24 01:22:22439 "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/ %U\n"
Jay Harris05b50bd2019-10-30 01:48:38440 "Icon=chrome-https__paint.app\n"
441 "Categories=Image\n"
Jay Harris05b50bd2019-10-30 01:48:38442 "StartupWMClass=paint.app\n"},
443
444 // Test evil mime type.
445 {"https://paint.app", "Evil Paint", "chrome-https__paint.app", "Image",
446 "image/png\nExec=rm -rf /", false,
447
448 "#!/usr/bin/env xdg-open\n"
449 "[Desktop Entry]\n"
450 "Version=1.0\n"
451 "Terminal=false\n"
452 "Type=Application\n"
453 "Name=Evil Paint\n"
454 "Exec=/opt/google/chrome/google-chrome --app=https://paint.app/\n"
455 "Icon=chrome-https__paint.app\n"
456 "Categories=Image\n"
457 "StartupWMClass=paint.app\n"}};
[email protected]07753f0f2013-02-05 07:52:59458
Avi Drissman22f82872018-12-25 23:09:07459 for (size_t i = 0; i < base::size(test_cases); i++) {
[email protected]b10392932011-03-08 21:28:14460 SCOPED_TRACE(i);
[email protected]a0b60cfd2011-04-06 18:02:41461 EXPECT_EQ(
462 test_cases[i].expected_output,
[email protected]06bfeb12014-05-27 14:00:09463 GetDesktopFileContents(
[email protected]14fbaed2013-05-02 07:54:02464 kChromeExePath,
[email protected]a0b60cfd2011-04-06 18:02:41465 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
Jay Harris05b50bd2019-10-30 01:48:38466 GURL(test_cases[i].url), std::string(),
467 base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
468 base::FilePath(), test_cases[i].categories, test_cases[i].mime_type,
Mandy Chend57606f2021-08-31 17:49:10469 test_cases[i].nodisplay, "", {}));
470 }
471}
472
473TEST(ShellIntegrationTest, GetDesktopFileContentsForApps) {
474 const base::FilePath kChromeExePath("/opt/google/chrome/google-chrome");
475 const struct {
476 const char* const url;
477 const char* const title;
478 const char* const icon_name;
479 bool nodisplay;
480 std::set<web_app::DesktopActionInfo> action_info;
481 const char* const expected_output;
482 } test_cases[] = {
483 // Test Shortcut Menu actions.
484 {"https://example.app",
485 "Lawful example",
486 "IconName",
487 false,
488 {
489 web_app::DesktopActionInfo("action1", "Action 1",
490 GURL("https://example.com/action1")),
491 web_app::DesktopActionInfo("action2", "Action 2",
492 GURL("https://example.com/action2")),
493 web_app::DesktopActionInfo("action3", "Action 3",
494 GURL("https://example.com/action3")),
495 web_app::DesktopActionInfo("action4", "Action 4",
496 GURL("https://example.com/action4")),
497 },
498
499 "#!/usr/bin/env xdg-open\n"
500 "[Desktop Entry]\n"
501 "Version=1.0\n"
502 "Terminal=false\n"
503 "Type=Application\n"
504 "Name=Lawful example\n"
505 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId\n"
506 "Icon=IconName\n"
507 "StartupWMClass=example.app\n"
508 "Actions=action1;action2;action3;action4\n\n"
509 "[Desktop Action action1]\n"
510 "Name=Action 1\n"
511 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
512 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
513 "action1\n\n"
514 "[Desktop Action action2]\n"
515 "Name=Action 2\n"
516 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
517 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
518 "action2\n\n"
519 "[Desktop Action action3]\n"
520 "Name=Action 3\n"
521 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
522 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
523 "action3\n\n"
524 "[Desktop Action action4]\n"
525 "Name=Action 4\n"
526 "Exec=/opt/google/chrome/google-chrome --app-id=TestAppId "
527 "--app-launch-url-for-shortcuts-menu-item=https://example.com/"
528 "action4\n"},
529 };
530
531 for (size_t i = 0; i < base::size(test_cases); i++) {
532 SCOPED_TRACE(i);
533 EXPECT_EQ(
534 test_cases[i].expected_output,
535 GetDesktopFileContents(
536 kChromeExePath,
537 web_app::GenerateApplicationNameFromURL(GURL(test_cases[i].url)),
538 GURL(test_cases[i].url), "TestAppId",
539 base::ASCIIToUTF16(test_cases[i].title), test_cases[i].icon_name,
540 base::FilePath(), "", "", test_cases[i].nodisplay, "",
541 test_cases[i].action_info));
[email protected]b96aa932009-08-12 21:34:49542 }
543}
[email protected]d81a63c02013-03-07 08:49:04544
[email protected]a3c25952013-05-02 13:16:06545TEST(ShellIntegrationTest, GetDirectoryFileContents) {
546 const struct {
thestigd0b662342015-01-16 08:21:19547 const char* const title;
548 const char* const icon_name;
549 const char* const expected_output;
[email protected]a3c25952013-05-02 13:16:06550 } test_cases[] = {
Nico Webereaa08412019-08-14 01:24:37551 // Real-world case.
552 {"Chrome Apps", "chrome-apps",
[email protected]a3c25952013-05-02 13:16:06553
Nico Webereaa08412019-08-14 01:24:37554 "[Desktop Entry]\n"
555 "Version=1.0\n"
556 "Type=Directory\n"
557 "Name=Chrome Apps\n"
558 "Icon=chrome-apps\n"},
[email protected]a3c25952013-05-02 13:16:06559
Nico Webereaa08412019-08-14 01:24:37560 // Make sure that empty icons are replaced by the chrome icon.
561 {"Chrome Apps", "",
[email protected]a3c25952013-05-02 13:16:06562
Nico Webereaa08412019-08-14 01:24:37563 "[Desktop Entry]\n"
564 "Version=1.0\n"
565 "Type=Directory\n"
566 "Name=Chrome Apps\n"
567#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
568 "Icon=google-chrome\n"
[email protected]a7e18f42013-12-19 02:10:37569#else
Nico Webereaa08412019-08-14 01:24:37570 "Icon=chromium-browser\n"
[email protected]a7e18f42013-12-19 02:10:37571#endif
Nico Webereaa08412019-08-14 01:24:37572 },
[email protected]a3c25952013-05-02 13:16:06573 };
574
Avi Drissman22f82872018-12-25 23:09:07575 for (size_t i = 0; i < base::size(test_cases); i++) {
[email protected]a3c25952013-05-02 13:16:06576 SCOPED_TRACE(i);
[email protected]06bfeb12014-05-27 14:00:09577 EXPECT_EQ(test_cases[i].expected_output,
578 GetDirectoryFileContents(base::ASCIIToUTF16(test_cases[i].title),
579 test_cases[i].icon_name));
[email protected]a3c25952013-05-02 13:16:06580 }
581}
[email protected]06bfeb12014-05-27 14:00:09582
Robert Woods954a2182020-03-20 06:08:27583TEST(ShellIntegrationTest, GetMimeTypesRegistrationFilename) {
584 const struct {
585 const char* const profile_path;
586 const char* const app_id;
587 const char* const expected_filename;
588 } test_cases[] = {
589 {"Default", "app-id", "-app-id-Default.xml"},
590 {"Default Profile", "app-id", "-app-id-Default_Profile.xml"},
591 {"foo/Default", "app-id", "-app-id-Default.xml"},
592 {"Default*Profile", "app-id", "-app-id-Default_Profile.xml"}};
593 std::string browser_name(chrome::kBrowserProcessExecutableName);
594
595 for (const auto& test_case : test_cases) {
596 const base::FilePath filename =
597 GetMimeTypesRegistrationFilename(base::FilePath(test_case.profile_path),
598 web_app::AppId(test_case.app_id));
599 EXPECT_EQ(browser_name + test_case.expected_filename, filename.value());
600 }
601}
602
603TEST(ShellIntegrationTest, GetMimeTypesRegistrationFileContents) {
604 apps::FileHandlers file_handlers;
605 {
606 apps::FileHandler file_handler;
607 {
608 apps::FileHandler::AcceptEntry accept_entry;
609 accept_entry.mime_type = "application/foo";
610 accept_entry.file_extensions.insert(".foo");
611 file_handler.accept.push_back(accept_entry);
612 }
613 file_handlers.push_back(file_handler);
614 }
615 {
616 apps::FileHandler file_handler;
617 {
618 apps::FileHandler::AcceptEntry accept_entry;
619 accept_entry.mime_type = "application/foobar";
620 accept_entry.file_extensions.insert(".foobar");
621 file_handler.accept.push_back(accept_entry);
622 }
623 file_handlers.push_back(file_handler);
624 }
625 {
626 apps::FileHandler file_handler;
627 {
628 apps::FileHandler::AcceptEntry accept_entry;
629 accept_entry.mime_type = "application/bar";
630 accept_entry.file_extensions.insert(".bar");
631 accept_entry.file_extensions.insert(".baz");
632 file_handler.accept.push_back(accept_entry);
633 }
634 file_handlers.push_back(file_handler);
635 }
636
637 const std::string file_contents =
638 GetMimeTypesRegistrationFileContents(file_handlers);
639 const std::string expected_file_contents =
640 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
641 "<mime-info "
642 "xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
643 " <mime-type type=\"application/foo\">\n"
644 " <glob pattern=\"*.foo\"/>\n"
645 " </mime-type>\n"
646 " <mime-type type=\"application/foobar\">\n"
647 " <glob pattern=\"*.foobar\"/>\n"
648 " </mime-type>\n"
649 " <mime-type type=\"application/bar\">\n"
650 " <glob pattern=\"*.bar\"/>\n"
651 " <glob pattern=\"*.baz\"/>\n"
652 " </mime-type>\n"
653 "</mime-info>\n";
654
655 EXPECT_EQ(file_contents, expected_file_contents);
656}
657
Alexander Dunaev1016e632021-08-10 15:13:34658// The WM class name may be either capitalised or not, depending on the
659// platform.
660void CheckProgramClassClass(const std::string& class_name) {
661#if defined(USE_OZONE)
662 if (features::IsUsingOzonePlatform() &&
663 ui::OzonePlatform::GetPlatformNameForTest() != "x11") {
664 EXPECT_EQ("foo", class_name);
665 } else {
666 EXPECT_EQ("Foo", class_name);
667 }
668#else
669 EXPECT_EQ("foo", class_name);
670#endif
671}
672
thomasanderson12d87582016-07-29 21:17:41673TEST(ShellIntegrationTest, WmClass) {
674 base::CommandLine command_line((base::FilePath()));
675 EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
Alexander Dunaev1016e632021-08-10 15:13:34676 CheckProgramClassClass(
677 internal::GetProgramClassClass(command_line, "foo.desktop"));
thomasanderson12d87582016-07-29 21:17:41678
679 command_line.AppendSwitchASCII("class", "baR");
680 EXPECT_EQ("foo", internal::GetProgramClassName(command_line, "foo.desktop"));
681 EXPECT_EQ("baR", internal::GetProgramClassClass(command_line, "foo.desktop"));
682
683 command_line = base::CommandLine(base::FilePath());
684 command_line.AppendSwitchASCII("user-data-dir", "/tmp/baz");
685 EXPECT_EQ("foo (/tmp/baz)",
686 internal::GetProgramClassName(command_line, "foo.desktop"));
Alexander Dunaev1016e632021-08-10 15:13:34687 CheckProgramClassClass(
688 internal::GetProgramClassClass(command_line, "foo.desktop"));
thomasanderson12d87582016-07-29 21:17:41689}
690
[email protected]06bfeb12014-05-27 14:00:09691} // namespace shell_integration_linux